-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
515 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace CK.Monitoring.InterProcess | ||
{ | ||
/// <summary> | ||
/// Defines the final status of a <see cref=""/> | ||
/// </summary> | ||
public enum LogReceiverEndStatus | ||
{ | ||
None, | ||
Normal, | ||
MissingEndMarker, | ||
Error | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
using CK.Core; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.IO.Pipes; | ||
using System.Text; | ||
using System.Threading; | ||
|
||
namespace CK.Monitoring.InterProcess | ||
{ | ||
/// <summary> | ||
/// This receiver is the server of a <see cref="SimplePipeSenderActivityMonitorClient"/>. | ||
/// It creates a thread that receives the log entries from the external client and inject | ||
/// them in a local target monitor. | ||
/// <para> | ||
/// This receiver and its associated sender client cover a simple scenario where a process | ||
/// launch another simple process that uses one and only one monitor. The logs emitted by the | ||
/// child process appear to be from the parent process, incorporated in the activity of the | ||
/// parent process local monitor. | ||
/// </para> | ||
/// </summary> | ||
public class SimpleLogPipeReceiver : IDisposable | ||
{ | ||
readonly AnonymousPipeServerStream _server; | ||
readonly CKBinaryReader _reader; | ||
readonly IActivityMonitor _monitor; | ||
readonly Thread _thread; | ||
readonly bool _interProcess; | ||
LogReceiverEndStatus _endFlag; | ||
|
||
SimpleLogPipeReceiver( IActivityMonitor m, bool interProcess ) | ||
{ | ||
_interProcess = interProcess; | ||
var inherit = interProcess ? HandleInheritability.Inheritable : HandleInheritability.None; | ||
_server = new AnonymousPipeServerStream( PipeDirection.In, inherit ); | ||
_reader = new CKBinaryReader( _server ); | ||
_monitor = m; | ||
PipeName = _server.GetClientHandleAsString(); | ||
_thread = new Thread( Run ); | ||
_thread.IsBackground = true; | ||
_thread.Start(); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the pipe handler name that must be transmitted to the <see cref="SimplePipeSenderActivityMonitorClient"/>. | ||
/// </summary> | ||
public string PipeName { get; } | ||
|
||
/// <summary> | ||
/// Waits for the termination of the other side. | ||
/// If it is known that the client has failed (typically because external process ended with a non zero | ||
/// return code), <paramref name="otherFailed"/> should be true: this receiver will only wait for 500 ms | ||
/// before returning, avoiding to wait for the internal thread termination. | ||
/// When <paramref name="otherFailed"/> is false, this method blocks until the client sends its goodbye | ||
/// message or the pipe is broken. | ||
/// </summary> | ||
/// <param name="otherFailed">True when you already know that the sender has failed.</param> | ||
/// <returns>The final status.</returns> | ||
public LogReceiverEndStatus WaitEnd( bool otherFailed ) | ||
{ | ||
if( otherFailed ) | ||
{ | ||
if( !_thread.Join( 500 ) ) _endFlag = LogReceiverEndStatus.Error; | ||
} | ||
else _thread.Join(); | ||
return _endFlag; | ||
} | ||
|
||
/// <summary> | ||
/// Waits for the termination of the internal thread and closes the pipe. | ||
/// Even if this can be called immediately, you should first call <see cref="WaitEnd(bool)"/> | ||
/// before calling Dispose. | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
if( _endFlag == LogReceiverEndStatus.None ) _thread.Join(); | ||
_reader.Dispose(); | ||
_server.Dispose(); | ||
} | ||
|
||
void Run() | ||
{ | ||
try | ||
{ | ||
int streamVersion = _reader.ReadInt32(); | ||
if( _interProcess ) _server.DisposeLocalCopyOfClientHandle(); | ||
for(; ; ) | ||
{ | ||
var e = LogEntry.Read( _reader, streamVersion, out bool badEndOfStream ); | ||
if( e == null || badEndOfStream ) | ||
{ | ||
_endFlag = badEndOfStream ? LogReceiverEndStatus.MissingEndMarker : LogReceiverEndStatus.Normal; | ||
break; | ||
} | ||
switch( e.LogType ) | ||
{ | ||
case LogEntryType.Line: | ||
if( _monitor.ShouldLogLine( e.LogLevel, e.FileName, e.LineNumber ) ) | ||
{ | ||
_monitor.UnfilteredLog( e.Tags, e.LogLevel | LogLevel.IsFiltered, e.Text, e.LogTime, CKException.CreateFrom( e.Exception ), e.FileName, e.LineNumber ); | ||
} | ||
break; | ||
case LogEntryType.OpenGroup: | ||
_monitor.UnfilteredOpenGroup( _monitor.ShouldLogGroup( e.LogLevel, e.FileName, e.LineNumber ) | ||
? new ActivityMonitorGroupData( e.LogLevel | LogLevel.IsFiltered, e.Tags, e.Text, e.LogTime, CKException.CreateFrom( e.Exception ), null, e.FileName, e.LineNumber ) | ||
: null ); | ||
break; | ||
case LogEntryType.CloseGroup: | ||
_monitor.CloseGroup( e.LogTime, e.Conclusions ); | ||
break; | ||
} | ||
} | ||
} | ||
catch( Exception ex ) | ||
{ | ||
_endFlag = LogReceiverEndStatus.Error; | ||
ActivityMonitor.CriticalErrorCollector.Add( ex, $"While {nameof( SimpleLogPipeReceiver)}.Run." ); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Starts a receiver. | ||
/// <para> | ||
/// Its <see cref="PipeName"/> must be given to the <see cref="SimplePipeSenderActivityMonitorClient"/> | ||
/// (typically with a /logpipe: argument for the launched process) and <see cref="WaitEnd(bool)"/> should be | ||
/// called before disposing it. | ||
/// </para> | ||
/// <para> | ||
/// Once the child process has been started, no more logs should be emitted in the local monitor: the internal thread | ||
/// will receive the logs from the external client and relay them into the local monitor. | ||
/// </para> | ||
/// </summary> | ||
/// <param name="localMonitor">The local monitor to which all collected logs will be injected.</param> | ||
/// <param name="interProcess">True when the client will be created in another process. False for an intra-process client (but why would you need this?).</param> | ||
/// <returns>A started receiver, ready to inject external logs into the local monitor.</returns> | ||
public static SimpleLogPipeReceiver Start( IActivityMonitor localMonitor, bool interProcess = true ) => new SimpleLogPipeReceiver( localMonitor, interProcess ); | ||
|
||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
CK.Monitoring/InterProcess/SimplePipeSenderActivityMonitorClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using CK.Core; | ||
using CK.Monitoring; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO.Pipes; | ||
using System.Text; | ||
|
||
namespace CK.Monitoring.InterProcess | ||
{ | ||
/// <summary> | ||
/// Simple activity monitor client that uses a <see cref="AnonymousPipeClientStream"/> to sends log | ||
/// entries to a <see cref="SimpleLogPipeReceiver"/>. | ||
/// </summary> | ||
public class SimplePipeSenderActivityMonitorClient : IActivityMonitorClient, IDisposable | ||
{ | ||
readonly CKBinaryWriter _writer; | ||
readonly AnonymousPipeClientStream _client; | ||
bool _disposed; | ||
|
||
/// <summary> | ||
/// Initializes a new <see cref="SimplePipeSenderActivityMonitorClient"/>. | ||
/// </summary> | ||
/// <param name="pipeHandlerName">The name of the server pipe.</param> | ||
public SimplePipeSenderActivityMonitorClient( string pipeHandlerName ) | ||
{ | ||
_client = new AnonymousPipeClientStream( PipeDirection.Out, pipeHandlerName ); | ||
_writer = new CKBinaryWriter( _client ); | ||
_writer.Write( LogReader.CurrentStreamVersion ); | ||
} | ||
|
||
/// <summary> | ||
/// Sends a goodbye message (a zero byte) and closes this side of the pipe. | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
if( !_disposed ) | ||
{ | ||
_disposed = true; | ||
_client.WriteByte( 0 ); | ||
_writer.Dispose(); | ||
_client.Dispose(); | ||
} | ||
} | ||
|
||
void IActivityMonitorClient.OnAutoTagsChanged( CKTrait newTrait ) | ||
{ | ||
} | ||
|
||
void IActivityMonitorClient.OnGroupClosed( IActivityLogGroup group, IReadOnlyList<ActivityLogGroupConclusion> conclusions ) | ||
{ | ||
LogEntry.WriteCloseGroup( _writer, group.GroupLevel, group.CloseLogTime, conclusions ); | ||
} | ||
|
||
void IActivityMonitorClient.OnGroupClosing( IActivityLogGroup group, ref List<ActivityLogGroupConclusion> conclusions ) | ||
{ | ||
} | ||
|
||
void IActivityMonitorClient.OnOpenGroup( IActivityLogGroup group ) | ||
{ | ||
LogEntry.WriteLog( _writer, true, group.GroupLevel, group.LogTime, group.GroupText, group.GroupTags, group.ExceptionData, group.FileName, group.LineNumber ); | ||
} | ||
|
||
void IActivityMonitorClient.OnTopicChanged( string newTopic, string fileName, int lineNumber ) | ||
{ | ||
} | ||
|
||
void IActivityMonitorClient.OnUnfilteredLog( ActivityMonitorLogData data ) | ||
{ | ||
LogEntry.WriteLog( _writer, false, data.Level, data.LogTime, data.Text, data.Tags, data.ExceptionData, data.FileName, data.LineNumber ); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.