mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
202 lines
6.9 KiB
C#
202 lines
6.9 KiB
C#
using GitHub.Runner.Sdk;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace GitHub.Runner.Common
|
|
{
|
|
public sealed class HostTraceListener : TextWriterTraceListener
|
|
{
|
|
private const string _logFileNamingPattern = "{0}_{1:yyyyMMdd-HHmmss}-utc.log";
|
|
private string _logFileDirectory;
|
|
private string _logFilePrefix;
|
|
private bool _enablePageLog = false;
|
|
private bool _enableLogRetention = false;
|
|
private int _currentPageSize;
|
|
private int _pageSizeLimit;
|
|
private int _retentionDays;
|
|
|
|
public HostTraceListener(string logFileDirectory, string logFilePrefix, int pageSizeLimit, int retentionDays)
|
|
: base()
|
|
{
|
|
ArgUtil.NotNullOrEmpty(logFileDirectory, nameof(logFileDirectory));
|
|
ArgUtil.NotNullOrEmpty(logFilePrefix, nameof(logFilePrefix));
|
|
_logFileDirectory = logFileDirectory;
|
|
_logFilePrefix = logFilePrefix;
|
|
|
|
Directory.CreateDirectory(_logFileDirectory);
|
|
|
|
if (pageSizeLimit > 0)
|
|
{
|
|
_enablePageLog = true;
|
|
_pageSizeLimit = pageSizeLimit * 1024 * 1024;
|
|
_currentPageSize = 0;
|
|
}
|
|
|
|
if (retentionDays > 0)
|
|
{
|
|
_enableLogRetention = true;
|
|
_retentionDays = retentionDays;
|
|
}
|
|
|
|
Writer = CreatePageLogWriter();
|
|
}
|
|
|
|
public HostTraceListener(string logFile)
|
|
: base()
|
|
{
|
|
ArgUtil.NotNullOrEmpty(logFile, nameof(logFile));
|
|
Directory.CreateDirectory(Path.GetDirectoryName(logFile));
|
|
Stream logStream = new FileStream(logFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, bufferSize: 4096);
|
|
Writer = new StreamWriter(logStream);
|
|
}
|
|
|
|
// Copied and modified slightly from .Net Core source code. Modification was required to make it compile.
|
|
// There must be some TraceFilter extension class that is missing in this source code.
|
|
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
|
|
{
|
|
if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null))
|
|
{
|
|
return;
|
|
}
|
|
|
|
WriteHeader(source, eventType, id);
|
|
WriteLine(message);
|
|
WriteFooter(eventCache);
|
|
}
|
|
|
|
public override void WriteLine(string message)
|
|
{
|
|
base.WriteLine(message);
|
|
if (_enablePageLog)
|
|
{
|
|
int messageSize = UTF8Encoding.UTF8.GetByteCount(message);
|
|
_currentPageSize += messageSize;
|
|
if (_currentPageSize > _pageSizeLimit)
|
|
{
|
|
Flush();
|
|
if (Writer != null)
|
|
{
|
|
Writer.Dispose();
|
|
Writer = null;
|
|
}
|
|
|
|
Writer = CreatePageLogWriter();
|
|
_currentPageSize = 0;
|
|
}
|
|
}
|
|
|
|
Flush();
|
|
}
|
|
|
|
public override void Write(string message)
|
|
{
|
|
base.Write(message);
|
|
if (_enablePageLog)
|
|
{
|
|
int messageSize = UTF8Encoding.UTF8.GetByteCount(message);
|
|
_currentPageSize += messageSize;
|
|
}
|
|
|
|
Flush();
|
|
}
|
|
|
|
internal bool IsEnabled(TraceOptions opts)
|
|
{
|
|
return (opts & TraceOutputOptions) != 0;
|
|
}
|
|
|
|
// Altered from the original .Net Core implementation.
|
|
private void WriteHeader(string source, TraceEventType eventType, int id)
|
|
{
|
|
string type = null;
|
|
switch (eventType)
|
|
{
|
|
case TraceEventType.Critical:
|
|
type = "CRIT";
|
|
break;
|
|
case TraceEventType.Error:
|
|
type = "ERR ";
|
|
break;
|
|
case TraceEventType.Warning:
|
|
type = "WARN";
|
|
break;
|
|
case TraceEventType.Information:
|
|
type = "INFO";
|
|
break;
|
|
case TraceEventType.Verbose:
|
|
type = "VERB";
|
|
break;
|
|
default:
|
|
type = eventType.ToString();
|
|
break;
|
|
}
|
|
|
|
Write(StringUtil.Format("[{0:u} {1} {2}] ", DateTime.UtcNow, type, source));
|
|
}
|
|
|
|
// Copied and modified slightly from .Net Core source code to make it compile. The original code
|
|
// accesses a private indentLevel field. In this code it has been modified to use the getter/setter.
|
|
private void WriteFooter(TraceEventCache eventCache)
|
|
{
|
|
if (eventCache == null)
|
|
return;
|
|
|
|
IndentLevel++;
|
|
if (IsEnabled(TraceOptions.ProcessId))
|
|
WriteLine("ProcessId=" + eventCache.ProcessId);
|
|
|
|
if (IsEnabled(TraceOptions.ThreadId))
|
|
WriteLine("ThreadId=" + eventCache.ThreadId);
|
|
|
|
if (IsEnabled(TraceOptions.DateTime))
|
|
WriteLine("DateTime=" + eventCache.DateTime.ToString("o", CultureInfo.InvariantCulture));
|
|
|
|
if (IsEnabled(TraceOptions.Timestamp))
|
|
WriteLine("Timestamp=" + eventCache.Timestamp);
|
|
|
|
IndentLevel--;
|
|
}
|
|
|
|
private StreamWriter CreatePageLogWriter()
|
|
{
|
|
if (_enableLogRetention)
|
|
{
|
|
DirectoryInfo diags = new(_logFileDirectory);
|
|
var logs = diags.GetFiles($"{_logFilePrefix}*.log");
|
|
foreach (var log in logs)
|
|
{
|
|
if (log.LastWriteTimeUtc.AddDays(_retentionDays) < DateTime.UtcNow)
|
|
{
|
|
try
|
|
{
|
|
log.Delete();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// catch Exception and continue
|
|
// we shouldn't block logging and fail the runner if the runner can't delete an older log file.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
string fileName = StringUtil.Format(_logFileNamingPattern, _logFilePrefix, DateTime.UtcNow);
|
|
string logFile = Path.Combine(_logFileDirectory, fileName);
|
|
Stream logStream;
|
|
if (File.Exists(logFile))
|
|
{
|
|
logStream = new FileStream(logFile, FileMode.Append, FileAccess.Write, FileShare.Read, bufferSize: 4096);
|
|
}
|
|
else
|
|
{
|
|
logStream = new FileStream(logFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, bufferSize: 4096);
|
|
}
|
|
|
|
return new StreamWriter(logStream);
|
|
}
|
|
}
|
|
}
|