#region License // Copyright (c) 2010, Jasper Yeh. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of ClearCanvas Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, // OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY // OF SUCH DAMAGE. #endregion using System; using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; using System.Threading; using ClearCanvas.Common; using ClearCanvas.Desktop; using ClearCanvas.Desktop.Actions; using ClearCanvas.Desktop.Tools; using Nullstack.ClearCanvasEx.DevTools.Common; using Path = System.IO.Path; namespace Nullstack.ClearCanvasEx.DevTools.Logging { /// /// Tools for opening logs related to the viewer and the service. /// [GroupHint("openWksLog", "Nullstack.ClearCanvasEx.DevTools.Logs.Workstation.Open")] [MenuAction("openWksLog", "global-menus/mnDebug/mnWorkstationLog/mnOpen", "OpenWorkstationLog")] // [GroupHint("tailWksLog", "Nullstack.ClearCanvasEx.DevTools.Logs.Workstation.Tail")] [MenuAction("tailWksLog", "global-menus/mnDebug/mnWorkstationLog/mnTail", "TailWorkstationLog")] // [GroupHint("deskWksLog", "Nullstack.ClearCanvasEx.DevTools.Logs.Workstation.Desk")] [MenuAction("deskWksLog", "global-menus/mnDebug/mnWorkstationLog/mnMonitor", "DeskWorkstationLog")] // [GroupHint("openSrvLog", "Nullstack.ClearCanvasEx.DevTools.Logs.Service.Open")] [MenuAction("openSrvLog", "global-menus/mnDebug/mnServiceLog/mnOpen", "OpenServiceLog")] // [GroupHint("tailSrvLog", "Nullstack.ClearCanvasEx.DevTools.Logs.Service.Tail")] [MenuAction("tailSrvLog", "global-menus/mnDebug/mnServiceLog/mnTail", "TailServiceLog")] // [GroupHint("deskSrvLog", "Nullstack.ClearCanvasEx.DevTools.Logs.Service.Desk")] [MenuAction("deskSrvLog", "global-menus/mnDebug/mnServiceLog/mnMonitor", "DeskServiceLog")] // [ExtensionOf(typeof (DesktopToolExtensionPoint))] public class AppLogViewerTool : Tool { private const string _workstationLogFilename = "logs\\ClearCanvas.Workstation.log"; private const string _serviceLogFilename = "logs\\ClearCanvas.Server.ShredHostService.log"; private static readonly Regex _commandLinePattern = new Regex("((?:\"[^\"]+\")|\\S+)(?: (.*))?", RegexOptions.Compiled | RegexOptions.Singleline); public static string WorkstationLogFile { get { var process = CCProcess.GetDesktopProcess(); return process != null ? FindLogFile(process.WorkingDirectory, _workstationLogFilename, _serviceLogFilename) : string.Empty; } } public static string ServiceLogFile { get { var process = CCProcess.GetServiceProcess(); return process != null ? FindLogFile(process.WorkingDirectory, _serviceLogFilename, _workstationLogFilename) : string.Empty; } } private static string FindLogFile(string basePath, params string[] possibleFilenames) { foreach (var possibleFilename in possibleFilenames) { var path = Path.Combine(basePath, possibleFilename); if (File.Exists(path)) return path; } return string.Empty; } private Shelf _shelfWorkstation; private Shelf _shelfService; /// /// Launches the workstation log in an external viewer /// public void OpenWorkstationLog() { LaunchFile(WorkstationLogFile); } /// /// Launches the last few kb of the workstation log in an external viewer /// public void TailWorkstationLog() { LaunchRecentFile(WorkstationLogFile); } /// /// Launches the workstation log in the viewer component /// public void DeskWorkstationLog() { // note: we use this field to ensure we don't open multiple copies of the same log viewer if (_shelfWorkstation == null) { var file = WorkstationLogFile; if (string.IsNullOrEmpty(file) || !File.Exists(file)) return; var component = new LogViewerComponent(file); _shelfWorkstation = ApplicationComponent.LaunchAsShelf(Context.DesktopWindow, component, SR.szWorkstationLog, "DesktopWorkstationLog", ShelfDisplayHint.DockFloat); _shelfWorkstation.Closed += OnWorkstationShelfClosed; } } /// /// callback for when the viewer closes /// /// /// private void OnWorkstationShelfClosed(object sender, ClosedEventArgs e) { _shelfWorkstation = null; } /// /// Launches the service log in the viewer component /// public void DeskServiceLog() { // note: we use this field to ensure we don't open multiple copies of the same log viewer if (_shelfService == null) { var file = ServiceLogFile; if (string.IsNullOrEmpty(file) || !File.Exists(file)) return; var component = new LogViewerComponent(file); _shelfService = ApplicationComponent.LaunchAsShelf(Context.DesktopWindow, component, SR.szServiceLog, "DesktopServiceLog", ShelfDisplayHint.DockFloat); _shelfService.Closed += OnServiceShelfClosed; } } /// /// callback for when the viewer closes /// /// /// private void OnServiceShelfClosed(object sender, ClosedEventArgs e) { _shelfService = null; } /// /// Opens the service log in an external viewer. /// public void OpenServiceLog() { LaunchFile(ServiceLogFile); } /// /// Opens the last few KB of the service log in an external viewer. /// public void TailServiceLog() { LaunchRecentFile(ServiceLogFile); } /// /// Helper method to copy the last 4 KB of the specified file into a temp file and launch it using the external viewer /// /// The filename private static void LaunchRecentFile(string basefile) { const int tailSize = 4096; const int bufferSize = 100; if (!File.Exists(basefile)) return; try { string tempfile = Path.GetTempFileName(); FileStream outfile = File.Open(tempfile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); FileStream fs = File.Open(basefile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] buffer = new byte[bufferSize]; if (fs.Length > tailSize) { fs.Seek(-tailSize, SeekOrigin.End); } int count; do { count = fs.Read(buffer, 0, bufferSize); if (count > 0) { outfile.Write(buffer, 0, count); } } while (count > 0); outfile.Close(); fs.Close(); string xfile = tempfile + ".txt"; File.Move(tempfile, xfile); int pid = LaunchFile(xfile); ProcessWaiter pw = new ProcessWaiter(pid, xfile); Thread t = new Thread(new ThreadStart(pw.Execute)); t.IsBackground = true; t.Start(); } catch (IOException) {} } /// /// Launches the file using the configured external viewer /// /// The filename /// Returns the process id of the external viewer private static int LaunchFile(string filename) { string command = filename; string arguments = ""; if (!File.Exists(filename)) return 0; if (!ConfigurationHelper.LogViewAppPathIsEmpty) { FileInfo fi = new FileInfo(filename); Match m = _commandLinePattern.Match(ConfigurationHelper.LogViewAppPath); if (m.Success) { command = m.Groups[1].Value; command = command.Replace("%n", fi.Name); command = command.Replace("%p", fi.FullName); command = command.Replace("%d", fi.DirectoryName); command = command.Replace("%x", fi.Extension); command = command.Replace("%%", "%"); arguments = m.Groups[2].Value; arguments = arguments.Replace("%n", fi.Name); arguments = arguments.Replace("%p", fi.FullName); arguments = arguments.Replace("%d", fi.DirectoryName); arguments = arguments.Replace("%x", fi.Extension); arguments = arguments.Replace("%%", "%"); } else { command = ConfigurationHelper.LogViewAppPath; } } ProcessStartInfo nfo = new ProcessStartInfo(command, arguments); nfo.WorkingDirectory = Environment.CurrentDirectory; Process p = Process.Start(nfo); return p.Id; } /// /// A class to delete a file after a certain process ends /// private class ProcessWaiter { private readonly int _pid; private readonly string _filename; public ProcessWaiter(int pid, string filename) { _pid = pid; _filename = filename; } public void Execute() { Process p = Process.GetProcessById(_pid); p.WaitForExit(); File.Delete(_filename); } } } }