The SessionLogging example demonstrates a number of logging capabilities using the NLog adapter in DCF.
Classes
Class | Description | |
---|---|---|
Program |
Demonstrate session based logging and configuring a rotating log, works by creating a separate log file for each association.
|
Examples
SessionLogging Sample Code
public class Program { private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); /// <summary> /// Maintains the list of seen associations, identified by their session id. /// </summary> /// <remarks> /// This list is appended to in the callback store server's endAssociation. /// </remarks> private static readonly List<string> SessionIds = new List<string>(); private const int NumberOfStoreJobs = 5; /// <summary> /// Main entry point for example. /// </summary> [STAThread] private static void Main() { AssociationManager mgr = null; try { LogManager.LogAdapter = new NLogLogAdapter(); // Enable debugging for the default session Framework.DefaultSessionSettings.IsDebugEnabled = true; mgr = StartSCP(104); StoreSCU scu = new StoreSCU(); for (int i = 0; i < NumberOfStoreJobs; i++) { Logger.InfoFormat("Submitting store job {0}...", i); scu.SubmitStoreJob(CreateStoreJob()); } // Stop the SCP, waiting for any currently running associations to finish if (mgr != null && mgr.Running) mgr.Stop(); // Flush any buffered log messages out to disk. LogManager.LogAdapter.Flush(); // Get the log directory path from the nlog configuration in our app config string logDir = GetNLogLogDirectoryPath("SessionFileLogger"); // Prompts the user to view selected log file contents ViewLogFiles(logDir); } catch (Exception e) { Console.WriteLine("Exception caught during execution: {0}", e); // Shutdown the SCP, terminating any current associations if (mgr != null && mgr.Running) mgr.Shutdown(); } if (!Debugger.IsAttached) return; Console.Write("Press any key to continue . . . "); Console.ReadKey(); } private static StoreJobDescription CreateStoreJob() { StoreJobDescription sjd = new StoreJobDescription(); sjd.CalledAETitle = "SCP"; sjd.CallingAETitle = "SCU"; sjd.CalledHost = "localhost"; sjd.CalledPort = "104"; sjd.AddInstance(new DicomInstanceInfo("mr-knee.dcm")); return sjd; } private static AssociationManager StartSCP(int port) { CallbackStoreServer storeServer = new CallbackStoreServer(); AssociationManager mgr = new AssociationManager(); mgr.ServerTcpPort = port; mgr.AssociationConfigPolicyMgr = storeServer; mgr.AddAssociationListener(storeServer); // AssociationManager.run() blocks, so start him listening for connections on a separate thread Thread t = new Thread(mgr.Run); t.Start(); if (!mgr.WaitForRunning(2000)) { throw new TimeoutException("AssociationManager did not start in an acceptable amount of time"); } Console.WriteLine("SCP started and listening on {0}...", port); return mgr; } /// <summary> /// Prompts the user for an entry selection to view a given log file in notepad. The list of log files /// will reflect only log files whose session id matches one of the listed session id's encountered during /// the execution of this example. /// </summary> /// <param name="logDir">The output log directory where the log files were written to.</param> private static void ViewLogFiles(string logDir) { if (String.IsNullOrEmpty(logDir)) throw new ArgumentException("invalid logDir"); if (SessionIds == null || SessionIds.Count == 0) throw new InvalidOperationException("no session ids!"); if (!Directory.Exists(logDir)) throw new DirectoryNotFoundException(logDir); Dictionary<int, string> map = CreateLogFileMap(logDir); try { for (; ; ) { Console.WriteLine("{0}Log output files in {1}:{0}", Environment.NewLine, logDir); for (int i = 0; i < map.Count; i++) { Console.WriteLine("{0}: {1}", i, map[i]); } string selection; int entry; for (; ; ) { Console.Write("{0}Input index to view contents of log file (press 'q' to quit): ", Environment.NewLine); selection = Console.ReadLine(); if (IsUserQuit(selection)) return; if (Int32.TryParse(selection, out entry) && entry >= 0 && entry < map.Count) break; Console.WriteLine("Invalid entry. Please re-enter selection..."); } Console.WriteLine("Entry({0}): Contents of log file ({1}):", selection, map[entry]); Process.Start("notepad.exe", map[entry]); } } finally { Console.Write("Delete created log files from this example? (y or n): "); string answer = Console.ReadLine(); if (answer != null && (answer.ToLower().Trim().Equals("y") || answer.ToLower().Trim().Equals("yes"))) { if (map != null) { Console.WriteLine("Deleting log files..."); foreach (string file in map.Values) { // we know the extension for this example is .log, just paranoia if (file.EndsWith(".log")) File.Delete(file); } string archiveDir = Path.Combine(logDir, "Archive"); if (Directory.Exists(archiveDir)) Directory.Delete(archiveDir, true); } } } } private static bool IsUserQuit(string txt) { if (txt == null) return false; txt = txt.ToLower().Trim(); return txt == "q" || txt == "quit" || txt == "exit"; } /// <summary> /// Creates a simple mapping between a selection number and a log file. Used to prompt the user for viewing a log file. /// </summary> /// <param name="logDir">The log directory.</param> /// <returns>The mapping for a given entry number and log file path.</returns> private static Dictionary<int, string> CreateLogFileMap(string logDir) { Dictionary<int, string> map = new Dictionary<int, string>(); string defaultSessionLogFile = Path.Combine(logDir, Process.GetCurrentProcess().ProcessName + ".log"); int selectionNumber = 0; map.Add(selectionNumber++, defaultSessionLogFile); foreach (string sessionId in SessionIds) { map.Add(selectionNumber++, Path.Combine(logDir, sessionId + ".log")); } return map; } /// <summary> /// NLog magic to get the file name associated with a logging target name. Dig through any /// wrapper filters until we get to a file target and return the directory name. /// </summary> /// <param name="targetName">The target name.</param> /// <returns>The path to the NLog directory.</returns> private static string GetNLogLogDirectoryPath(string targetName) { Target target = NLog.LogManager.Configuration.FindTargetByName(targetName); if (target == null) throw new ArgumentException("target(" + targetName + ") not found"); while (target is WrapperTargetBase) { target = (target as WrapperTargetBase).WrappedTarget; } FileTarget fileTarget = target as FileTarget; if (fileTarget == null) throw new ArgumentException("could not find file target for target(" + targetName + ")"); // NLog does not give up the path of the target easily... LogEventInfo logEventInfo = new LogEventInfo { TimeStamp = DateTime.Now }; string fileName = fileTarget.FileName.Render(logEventInfo); string directoryName = Path.GetDirectoryName(fileName) ?? "."; return directoryName; } /// <summary> /// Simple store client used to submit a store job to the listening store SCP and /// listen for the status of the job. /// </summary> private class StoreSCU : IStoreClientListener { private static readonly ILogger SCULogger = LogManager.GetCurrentClassLogger(); private readonly StoreClient _client = new StoreClient(); /// <summary> /// Submit the given store job using this classes store client. /// </summary> /// <param name="job">The job to submit.</param> public void SubmitStoreJob(StoreJobDescription job) { _client.SubmitStoreJob(job, this); } /// <summary> /// Implementation of StoreClientListener interface. /// </summary> public void StoreObjectComplete(StoreJobInstanceStatus status) { int base10DimseStatus = status.DimseStatusCode; string hexDimseStatus = base10DimseStatus.ToString("X"); string stringDimseStatus = StoreDimseStatus.GetStatusAsString(status.DimseStatusCode); SCULogger.InfoFormat("Received storeObjectComplete event status({0}) (0x{1}), {2}", base10DimseStatus, hexDimseStatus, stringDimseStatus); } /// <summary> /// Implementation of StoreClientListener interface. /// </summary> public void StoreJobComplete(StoreJobStatus status) { SCULogger.InfoFormat("Received storeJobComplete event {0}status = {1}{0}statusInfo = {2}", Environment.NewLine, JobStatus.GetStatusAsString(status.Status), JobStatusInfo.GetStatusAsString(status.StatusInfo)); } } /// <summary> /// Simple callback store server used to handle association and CStore requests. /// </summary> private class CallbackStoreServer : AssociationListenerAdapter, IAssociationConfigPolicyManager { private static readonly ILogger StoreServerLogger = LogManager.GetCurrentClassLogger(); private readonly List<LogDebugFlags> debugFlagList = new List<LogDebugFlags> { DF.None, DF.Connection, DF.Connection | DF.DimseReadSummary | DF.DimseWriteSummary, DF.Connection | DF.DimseWire | DF.AcsePdu | DF.DataPdu, DF.All, }; private int _debugFlagIndex /*= 0*/; /// <summary> /// Returns a DicomSessionSettings object to be used for the association. /// </summary> /// <param name="assoc">The AssociationAcceptor for the given association</param> /// <returns>A default DicomSessionSettings object</returns> public DicomSessionSettings GetSessionSettings(AssociationAcceptor assoc) { DicomSessionSettings ss = new DicomSessionSettings(); ss.IsDebugEnabled = true; ss.DebugFlags = debugFlagList[_debugFlagIndex++]; _debugFlagIndex %= debugFlagList.Count; // Before each log message, output the connection id. See the SessionLogging's app config for how to // configure the layout to output the log context for a connection id. ss.LogContext.Add("ConnectionId", assoc.ConnectionID); return ss; } /// <summary> /// Handle the association that caused this 'beginAssociation' method to be called. /// </summary> /// <param name="assoc">The AssociationAcceptor for the given association</param> public override void BeginAssociation(AssociationAcceptor assoc) { StoreServerLogger.InfoFormat(assoc.SessionSettings, "beginAssociation: {0}", assoc.AssociationInfo); assoc.RegisterServiceClassProvider(new StoreSCP(assoc, null, CStore)); } /// <summary> /// Handle the association that caused this 'endAssociation' method to be called. /// </summary> /// <param name="assoc">The AssociationAcceptor for the given association</param> public override void EndAssociation(AssociationAcceptor assoc) { StoreServerLogger.InfoFormat(assoc.SessionSettings, "endAssociation: {0}", assoc.AssociationInfo); // Add the session id for this association to the list of seen session ids if not already string sessionId = assoc.SessionSettings.SessionId.ToString(); if (!SessionIds.Contains(sessionId)) SessionIds.Add(sessionId); } /// <summary> /// Writes some information from the incoming dataset to the console. /// </summary> /// <param name="acceptor">The AssociationAcceptor for the given association</param> /// <param name="request">The inbound CStoreRequest</param> /// <returns>A successful CStoreResponse</returns> private CStoreResponse CStore(AssociationAcceptor acceptor, CStoreRequest request) { CStoreResponse response = new CStoreResponse(request); StoreServerLogger.InfoFormat(acceptor.SessionSettings, "CallbackStoreServer: patient's name is {0} from {1} to {2}", request.Data.GetElementStringValue(Tags.PatientName), acceptor.AssociationInfo.CallingTitle, acceptor.AssociationInfo.CalledPresentationAddress); request.Data.ExpandStreamingModeData(false); // we don't need the pixel data for this example return response; } } }