Index: fedd/fedd.py
===================================================================
--- fedd/fedd.py	(revision 8ecfbad2099514e94953b018297a252323fc86fa)
+++ fedd/fedd.py	(revision 11a08b0e9216a4de8d8e50f742da2911382a40e7)
@@ -19,4 +19,7 @@
 
 from threading import *
+from signal import signal, pause, SIGINT, SIGTERM
+from select import select
+import logging
 
 # The SSL server here is based on the implementation described at
@@ -169,5 +172,5 @@
 
 	self.set_defaults(host="localhost", port=23235, transport="soap",
-		debug=0)
+		logfile=None, debug=0)
 
 	self.add_option("-d", "--debug", action="count", dest="debug", 
@@ -177,4 +180,6 @@
 	self.add_option("-H", "--host", action="store", type="string",
 		dest="host", help="Hostname to listen on (default %default)")
+	self.add_option("-l", "--logfile", action="store", dest="logfile", 
+		help="File to send log messages to")
 	self.add_option("-p", "--port", action="store", type="int",
 		dest="port", help="Port to listen on (default %default)")
@@ -188,6 +193,24 @@
 		const=sys.stderr, help="Print SOAP exchange to stderr")
 
+servers_active = True
+
+log_params = {\
+	'format': "%(asctime)s %(levelname)-8s %(message)s",\
+	'datefmt': '%a, %d %b %Y %H:%M:%S'\
+    }
+
+def shutdown(sig, frame):
+    global servers_active, flog
+    servers_active = False
+    flog.info("Received signal %d, shutting down" % sig);
+
 def run_server(s):
-    if s: s.serve_forever()
+    global servers_active   # Not strictly needed: servers_active is only read
+
+    if s: 
+	while servers_active:
+	    i, o, e = select((s,), (), (), 5.0)
+	    if s in i:
+		s.handle_request()
 
 services = [ ]
@@ -195,4 +218,22 @@
 
 opts, args = fedd_opts().parse_args()
+
+flog = logging.getLogger("fedd")
+ffmt = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s",
+	'%a, %d %b %Y %H:%M:%S')
+
+if opts.logfile: fh = logging.FileHandler(opts.logfile)
+else: fh = logging.StreamHandler(sys.stdout)
+
+# The handler will print anything, setting the logger level will affect what
+# gets recorded.
+fh.setLevel(logging.DEBUG)
+
+if opts.debug: flog.setLevel(logging.DEBUG)
+else: flog.setLevel(logging.INFO)
+
+fh.setFormatter(ffmt)
+flog.addHandler(fh)
+
 
 if opts.configfile != None: 
@@ -239,7 +280,13 @@
     elif s[0] == "xmlrpc":
 	servers.append(fedd_server(s[1], fedd_xmlrpc_handler, ctx, impl))
-    else: print >>sys.stderr, "Unknown transport: %s" % s[0]
+    else: flog.warning("Unknown transport: %s" % s[0])
+
+signal(SIGINT, shutdown)
+signal(SIGTERM, shutdown)
 
 for s in servers:
-    t = Thread(target=run_server, args=(s,))
-    t.start()
+    if s:
+	t = Thread(target=run_server, args=(s,))
+	t.start()
+
+pause()
Index: fedd/fedd_access.py
===================================================================
--- fedd/fedd_access.py	(revision 8ecfbad2099514e94953b018297a252323fc86fa)
+++ fedd/fedd_access.py	(revision 11a08b0e9216a4de8d8e50f742da2911382a40e7)
@@ -22,4 +22,11 @@
 import parse_detail
 from service_error import *
+import logging
+
+class nullHandler(logging.Handler):
+    def emit(self, record): pass
+
+fl = logging.getLogger("fedd.access")
+fl.addHandler(nullHandler())
 
 class fedd_access:
@@ -52,4 +59,6 @@
 	self.fedid_default = config.fedid_default
 	self.restricted = copy.copy(config.restricted)
+
+	self.log = logging.getLogger("fedd.access")
 
 	# Certs are promoted from the generic to the specific, so without a
@@ -385,5 +394,4 @@
     def RequestAccess(self, req, fid):
 
-	print "in get access"
 	if req.has_key('RequestAccessRequestBody'):
 	    req = req['RequestAccessRequestBody']
@@ -393,6 +401,4 @@
 	if req.has_key('destinationTestbed'):
 	    dt = unpack_id(req['destinationTestbed'])
-
-	print dt, " ", self.testbed
 
 	if dt == None or dt == self.testbed:
Index: fedd/fedd_allocate_project.py
===================================================================
--- fedd/fedd_allocate_project.py	(revision 8ecfbad2099514e94953b018297a252323fc86fa)
+++ fedd/fedd_allocate_project.py	(revision 11a08b0e9216a4de8d8e50f742da2911382a40e7)
@@ -22,4 +22,14 @@
 import parse_detail
 from service_error import *
+import logging
+
+
+class nullHandler(logging.Handler):
+    def emit(self, record): pass
+
+fl = logging.getLogger("fedd.allocate.local")
+fl.addHandler(nullHandler())
+fl = logging.getLogger("fedd.allocate.remote")
+fl.addHandler(nullHandler())
 
 class fedd_allocate_project_local:
@@ -34,4 +44,5 @@
 	self.mkproj = '/usr/testbed/sbin/mkproj'
 	self.grantnodetype = '/usr/testbed/sbin/grantnodetype'
+	self.log = logging.getLogger("fedd.allocate.local")
 
     def random_string(self, s, n=3):
@@ -153,4 +164,5 @@
 	rc = 0
 	for cmd in cmds:
+	    self.log.debug("[dynamic_project]: %s" % ' '.join(cmd))
 	    if self.dynamic_projects:
 		try:
@@ -161,6 +173,4 @@
 			    "Dynamic project subprocess creation error "+ \
 				    "[%s] (%s)" %  (cmd[1], e.strerror))
-	    else:
-		print >>sys.stdout, str(" ").join(cmd)
 
 	    if rc != 0: 
Index: fedd/fedd_config_file.py
===================================================================
--- fedd/fedd_config_file.py	(revision 8ecfbad2099514e94953b018297a252323fc86fa)
+++ fedd/fedd_config_file.py	(revision 11a08b0e9216a4de8d8e50f742da2911382a40e7)
@@ -29,5 +29,5 @@
 	    "create_experiment_cert_file", "create_experiment_cert_pwd",
 	    "create_experiment_trusted_certs", "federation_script_dir",
-	    "ssh_pubkey_file", "experiment_state_file")
+	    "ssh_pubkey_file", "experiment_state_file", "experiment_log")
     id_list_attrs = ("restricted",)
 
Index: fedd/fedd_experiment_control.py
===================================================================
--- fedd/fedd_experiment_control.py	(revision 8ecfbad2099514e94953b018297a252323fc86fa)
+++ fedd/fedd_experiment_control.py	(revision 11a08b0e9216a4de8d8e50f742da2911382a40e7)
@@ -29,4 +29,12 @@
 import parse_detail
 from service_error import *
+
+import logging
+
+class nullHandler(logging.Handler):
+    def emit(self, record): pass
+
+fl = logging.getLogger("fedd.experiment_control")
+fl.addHandler(nullHandler())
 
 class fedd_experiment_control_local:
@@ -91,15 +99,12 @@
 		    self.exception = s
 		    if self.trace_file:
-			print >>self.trace_file, "Thread exception: %s %s" % \
-				(s.code_string(), s.desc)
-		    
+			logging.error("Thread exception: %s %s" % \
+				(s.code_string(), s.desc))
 		except:
 		    self.exception = sys.exc_info()[1]
 		    if self.trace_file:
-			print >>self.trace_file, \
-				"Unexpected thread exception: %s" % \
-				self.exception
-			print >>self.trace_file, "Trace: %s" % \
-				traceback.format_exc()
+			logging.error(("Unexpected thread exception: %s" +\
+				"Trace %s") % (self.exception,\
+				    traceback.format_exc()))
 	    if self.pdata:
 		self.pdata.terminate()
@@ -133,4 +138,5 @@
 	self.exp_stem = "fed-stem"
 	self.debug = config.create_debug
+	self.log = logging.getLogger("fedd.experiment_control")
 	self.muxmax = 2
 	self.nthreads = 2
@@ -176,4 +182,21 @@
 		raise service_error(service_error.internal,
 			"Cannot read sshpubkey")
+
+	# Set the logging level to the value passed in.  The getattr slieght of
+	# hand finds the logging level constant corrersponding to the string.
+	# We're a little paranoid to avoid user mayhem.
+	if config.experiment_log:
+	    try:
+		level = int(getattr(logging, config.experiment_log.upper(),-1))
+
+		if  logging.DEBUG <= level <= logging.CRITICAL:
+		    self.log.setLevel(level)
+		else:
+		    self.log.error("Bad experiment_log value: %s" % \
+			    config.experiment_log)
+
+	    except ValueError:
+		self.log.error("Bad experiment_log value: %s" % \
+			config.experiment_log)
 
 	# Grab saved state
@@ -262,8 +285,8 @@
 	    pickle.dump(self.state, f)
 	except IOError, e:
-	    print >>sys.stderr, "Can't write file %s: %s" % \
-		    (self.state_filename, e)
+	    self.log.error("Can't write file %s: %s" % \
+		    (self.state_filename, e))
 	except pickle.PicklingError, e:
-	    print >>sys.stderr, "Pickling problem: %s" % e
+	    self.log.error("Pickling problem: %s" % e)
 
     # Call while holding self.state_lock
@@ -273,8 +296,8 @@
 	    self.state = pickle.load(f)
 	except IOError, e:
-	    print >>sys.stderr, "Can't open %s: %s" % \
-		    (self.state_filename, e)
+	    self.log.warning("No saved state: Can't open %s: %s" % \
+		    (self.state_filename, e))
 	except pickle.UnpicklingError, e:
-	    print >>sys.stderr, "Unpickling failed: %s" % e
+	    self.log.warning("No saved state: Unpickling failed: %s" % e)
 
     def scp_file(self, file, user, host, dest=""):
@@ -284,20 +307,9 @@
 
 	scp_cmd = [self.scp_exec, file, "%s@%s:%s" % (user, host, dest)]
-
-	trace = self.trace_file
-	if not trace:
-	    try:
-		trace = open("/dev/null", "w")
-	    except IOError:
-		raise service_error(service_error.internal,
-			"Cannot open /dev/null??");
-
+	rv = 0
+
+	self.log.debug("[scp_file]: %s" % " ".join(scp_cmd))
 	if not self.debug:
 	    rv = call(scp_cmd, stdout=trace, stderr=trace)
-	else:
-	    if self.trace_file: 
-		print >>self.trace_file, "debug [scp_file]: %s" % \
-			" ".join(scp_cmd)
-	    rv = 0
 
 	return rv == 0
@@ -306,18 +318,9 @@
 	sh_str = "%s %s@%s %s" % (self.ssh_exec, user, host, cmd)
 
-	trace = self.trace_file
-	if not trace:
-	    try:
-		trace = open("/dev/null", "w")
-	    except IOError:
-		raise service_error(service_error.internal,
-			"Cannot open /dev/null??");
-
+	self.log.debug("[ssh_cmd]: %s" % sh_str)
 	if not self.debug:
 	    sub = Popen(sh_str, shell=True, stdout=trace, stderr=trace)
 	    return sub.wait() == 0
 	else:
-	    if self.trace_file:
-		print >>self.trace_file,"debug [ssh_cmd]: %s" % sh_str 
 	    return True
 
@@ -365,18 +368,12 @@
 	cmd = [self.ssh_exec, "%s@%s" % (user, host), expinfo_exec, pid, eid]
 
-
-	if self.trace_file:
-	    print >>self.trace_file, "status request: %s" % " ".join(cmd)
-	
-	if not self.trace_file:
-	    try:
-		st_file = open("/dev/null", "w")
-	    except IOError:
-		raise service_error(service_error.internal, 
-			"Cannot open /dev/null!?")
-	else:
-	    st_file = self.trace_file
-
-	status = Popen(cmd, stdout=PIPE, stderr=st_file)
+	self.log.debug("[start_segment]: %s"% " ".join(cmd))
+	dev_null = None
+	try:
+	    dev_null = open("/dev/null", "a")
+	except IOError, e:
+	    self.log.error("[start_segment]: can't open /dev/null: %s" %e)
+
+	status = Popen(cmd, stdout=PIPE, stderr=dev_null)
 	for line in status.stdout:
 	    m = state_re.match(line)
@@ -392,7 +389,7 @@
 	    raise service_error(service_error.internal,
 		    "Cannot get status of segment %s:%s/%s" % (tb, pid, eid))
-	if self.trace_file:
-	    print >>self.trace_file, "%s: %s" % (tb, state)
-	    print >>self.trace_file, "transferring experiment to %s" % tb
+
+	self.log.debug("[start_segment]: %s: %s" % (tb, state))
+	self.log.info("[start_segment]:transferring experiment to %s" % tb)
 
 	if not self.scp_file("%s/%s/%s" % (tmpdir, tb, tclfile), user, host):
@@ -433,6 +430,5 @@
 			"%s/rpms" % tmpdir, tarfiles_dir):
 		    return False
-	    if self.trace_file:
-		print >>self.trace_file, "Modifying %s on %s" % (eid, tb)
+	    self.log.info("[start_segment]: Modifying %s on %s" % (eid, tb))
 	    if not self.ssh_cmd(user, host,
 		    "/usr/testbed/bin/modexp -r -s -w %s %s %s" % \
@@ -459,12 +455,10 @@
 			"%s/rpms" % tmpdir, tarfiles_dir):
 		    return False
-	    if self.trace_file:
-		print >>self.trace_file, "Modifying %s on %s" % (eid, tb)
+	    self.log.info("[start_segment]: Modifying %s on %s" % (eid, tb))
 	    if not self.ssh_cmd(user, host,
 		    "/usr/testbed/bin/modexp -w %s %s %s" % (pid, eid, tclfile),
 		    "modexp"):
 		return False
-	    if self.trace_file:
-		print >>self.trace_file, "Swapping %s in on %s" % (eid, tb)
+	    self.log.info("[start_segment]: Swapping %s in on %s" % (eid, tb))
 	    if not self.ssh_cmd(user, host,
 		    "/usr/testbed/bin/swapexp -w %s %s in" % (pid, eid),
@@ -486,6 +480,5 @@
 			"%s/rpms" % tmpdir, tarfiles_dir):
 		    return False
-	    if self.trace_file:
-		print >>self.trace_file, "Creating %s on %s" % (eid, tb)
+	    self.log.info("[start_segment]: Creating %s on %s" % (eid, tb))
 	    if not self.ssh_cmd(user, host,
 		    "/usr/testbed/bin/startexp -i -f -w -p %s -e %s %s" % \
@@ -502,6 +495,5 @@
 		    proj_dir):
 		return False
-	    if self.trace_file:
-		print >>self.trace_file, "Swapping %s in on %s" % (eid, tb)
+	    self.log.info("[start_segment]: Swapping %s in on %s" % (eid, tb))
 	    if not self.ssh_cmd(user, host,
 		    "/usr/testbed/bin/swapexp -w %s %s in" % (pid, eid),
@@ -510,6 +502,5 @@
 	    return True
 	else:
-	    if self.trace_file:
-		print >>self.trace_file, "unknown state %s" % state
+	    self.log.debug("[start_segment]:unknown state %s" % state)
 	    return False
 
@@ -519,6 +510,5 @@
 	pid = tbparams[tb]['project']
 
-	if self.trace_file:
-	    print >>self.trace_file, "Stopping %s on %s" % (eid, tb)
+	self.log.info("[stop_segment]: Stopping %s on %s" % (eid, tb))
 	return self.ssh_cmd(user, host,
 		"/usr/testbed/bin/swapexp -w %s %s out" % (pid, eid))
@@ -534,16 +524,15 @@
 	t = type.lower();
 	if t not in valid_types: raise ValueError
-
-	trace = self.trace_file
-	if not trace:
-	    try:
-		trace = open("/dev/null", "w")
-	    except IOError:
-		raise service_error(service_error.internal,
-			"Cannot open /dev/null??");
+	cmd = [self.ssh_keygen, '-t', t, '-N', '', '-f', dest]
+
+	try:
+	    trace = open("/dev/null", "w")
+	except IOError:
+	    raise service_error(service_error.internal,
+		    "Cannot open /dev/null??");
 
 	# May raise CalledProcessError
-	rv = call([self.ssh_keygen, '-t', t, '-N', '', '-f', dest],
-		stdout=trace, stderr=trace)
+	self.log.debug("[generate_ssh_keys]: %s" % " ".join(cmd))
+	rv = call(cmd, stdout=trace, stderr=trace)
 	if rv != 0:
 	    raise service_error(service_error.internal, 
@@ -1007,7 +996,6 @@
 		    else:
 			# XXX
-			print >>sys.stderr, \
-			    "Ignoring gateways for unknown testbed %s" \
-				    % self.current_gateways
+			self.log.error("[gateways]: Ignoring gateways for " + \
+				"unknown testbed %s" % self.current_gateways)
 			self.current_gateways = None
 		    return True
@@ -1052,7 +1040,6 @@
 				    "Error creating seer config")
 		    else:
-			if self.trace_file:
-			    print >>sys.stderr, "No control gateway for %s" %\
-				    self.current_gateways
+			debug.error("[gateways]: No control gateway for %s" %\
+				    self.current_gateways)
 		    self.current_gateways = None
 		else:
@@ -1374,15 +1361,12 @@
 		    "Swap in failed on %s" % ",".join(failed))
 	else:
-	    if self.trace_file:
-		print >>self.trace_file, "Experiment started"
+	    self.log.info("[start_segment]: Experiment %s started" % eid)
 
 	# Generate an ID for the experiment (slice) and a certificate that the
 	# allocator can use to prove they own it.  We'll ship it back through
 	# the encrypted connection.
-	(expid, expcert) = generate_fedid("test", dir=tmpdir, 
-		trace=self.trace_file)
-
-	if self.trace_file:
-	    print >>self.trace_file, "removing %s" % tmpdir
+	(expid, expcert) = generate_fedid("test", dir=tmpdir, log=self.log)
+
+	self.log.debug("[start_experiment]: removing %s" % tmpdir)
 
 	# Walk up tmpdir, deleting as we go
Index: fedd/fedd_util.py
===================================================================
--- fedd/fedd_util.py	(revision 8ecfbad2099514e94953b018297a252323fc86fa)
+++ fedd/fedd_util.py	(revision 11a08b0e9216a4de8d8e50f742da2911382a40e7)
@@ -286,5 +286,5 @@
 
 
-def generate_fedid(subj, bits=2048, trace=None, dir=None):
+def generate_fedid(subj, bits=2048, log=None, dir=None, trace=None):
     """
     Create a new certificate and derive a fedid from it.
@@ -306,10 +306,10 @@
 		    "-x509", "-days", "30", "-out", certpath]
 
-	    if trace:
-		print >>trace, "calling %s" % " ".join(cmd)
-		call_out = trace
-	    else:
-		call_out = open("/dev/null", "w")
-		
+	    if log:
+		log.debug("[generate_fedid] %s" % " ".join(cmd))
+
+	    if trace: call_out = trace
+	    else: call_out = open("/dev/null", "w")
+
 	    rv = subprocess.call(cmd, stdout=call_out, stderr=call_out)
 	    if rv == 0:
