* Logging

* Doing dependency injection

* expose GetLoggers for better testing

Co-authored-by: stas <statis@microsoft.com>
This commit is contained in:
Stas
2022-04-02 15:36:08 -07:00
committed by GitHub
parent f11e79de4b
commit fbff3fc4af
3 changed files with 212 additions and 2 deletions

View File

@ -1,8 +1,21 @@
using System;
namespace Microsoft.OneFuzz.Service;
public enum LogDestination
{
Console,
AppInsights,
}
public static class EnvironmentVariables {
static EnvironmentVariables() {
LogDestinations = new LogDestination[] { LogDestination.AppInsights };
}
//TODO: Add environment variable to control where to write logs to
public static LogDestination[] LogDestinations { get; set; }
public static class AppInsights {
public static string? AppId { get => Environment.GetEnvironmentVariable("APPINSIGHTS_APPID"); }
public static string? InstrumentationKey { get => Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY"); }

View File

@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.DataContracts;
using System.Diagnostics;
namespace Microsoft.OneFuzz.Service;
public interface ILog {
void Log(Guid correlationId, String message, SeverityLevel level, IDictionary<string, string> tags, string? caller);
void LogEvent(Guid correlationId, String evt, IDictionary<string, string> tags, IDictionary<string, double>? metrics, string? caller);
void LogException(Guid correlationId, Exception ex, IDictionary<string, string> tags, IDictionary<string, double>? metrics, string? caller);
void Flush();
}
class AppInsights : ILog {
private TelemetryClient telemetryClient =
new TelemetryClient(
new TelemetryConfiguration(EnvironmentVariables.AppInsights.InstrumentationKey));
public void Log(Guid correlationId, String message, SeverityLevel level, IDictionary<string, string> tags, string? caller) {
tags.Add("Correlation ID", correlationId.ToString());
if (caller is not null) tags.Add("CalledBy", caller);
telemetryClient.TrackTrace(message, level, tags);
}
public void LogEvent(Guid correlationId, String evt, IDictionary<string, string> tags, IDictionary<string, double>? metrics, string? caller) {
tags.Add("Correlation ID", correlationId.ToString());
if (caller is not null) tags.Add("CalledBy", caller);
telemetryClient.TrackEvent(evt, properties: tags, metrics: metrics);
}
public void LogException(Guid correlationId, Exception ex, IDictionary<string, string> tags, IDictionary<string, double>? metrics, string? caller) {
tags.Add("Correlation ID", correlationId.ToString());
if (caller is not null) tags.Add("CalledBy", caller);
telemetryClient.TrackException(ex, tags, metrics);
}
public void Flush() {
telemetryClient.Flush();
}
}
//TODO: Should we write errors and Exception to std err ?
class Console : ILog {
private string DictToString<T>(IDictionary<string, T>? d) {
if (d is null)
{
return string.Empty;
}
else
{
return string.Join("", d);
}
}
private void LogTags(Guid correlationId, string? caller, IDictionary<string, string> tags) {
var ts = DictToString(tags);
if (!string.IsNullOrEmpty(ts))
{
System.Console.WriteLine("[{0}:{1}] Tags:{2}", correlationId, caller, ts);
}
}
private void LogMetrics(Guid correlationId, string? caller, IDictionary<string, double>? metrics) {
var ms = DictToString(metrics);
if (!string.IsNullOrEmpty(ms)) {
System.Console.Out.WriteLine("[{0}:{1}] Metrics:{2}", correlationId, caller, DictToString(metrics));
}
}
public void Log(Guid correlationId, String message, SeverityLevel level, IDictionary<string, string> tags, string? caller) {
System.Console.Out.WriteLine("[{0}:{1}][{2}] {3}", correlationId, caller, level, message);
LogTags(correlationId, caller, tags);
}
public void LogEvent(Guid correlationId, String evt, IDictionary<string, string> tags, IDictionary<string, double>? metrics, string? caller) {
System.Console.Out.WriteLine("[{0}:{1}][Event] {2}", correlationId, caller, evt);
LogTags(correlationId, caller, tags);
LogMetrics(correlationId, caller, metrics);
}
public void LogException(Guid correlationId, Exception ex, IDictionary<string, string> tags, IDictionary<string, double>? metrics, string? caller) {
System.Console.Out.WriteLine("[{0}:{1}][Exception] {2}", correlationId, caller, ex);
LogTags(correlationId, caller, tags);
LogMetrics(correlationId, caller, metrics);
}
public void Flush() {
System.Console.Out.Flush();
}
}
public class LogTracer {
private List<ILog> loggers;
private IDictionary<string, string> tags = new Dictionary<string, string>();
private Guid correlationId;
public LogTracer(Guid correlationId, List<ILog> loggers) {
this.correlationId = correlationId;
this.loggers = loggers;
}
public IDictionary<string, string> Tags => tags;
public void Info(string message) {
var caller = new StackTrace()?.GetFrame(1)?.GetMethod()?.Name;
foreach (var logger in loggers) {
logger.Log(correlationId, message, SeverityLevel.Information, Tags, caller);
}
}
public void Warning(string message) {
var caller = new StackTrace()?.GetFrame(1)?.GetMethod()?.Name;
foreach (var logger in loggers) {
logger.Log(correlationId, message, SeverityLevel.Warning, Tags, caller);
}
}
public void Error(string message)
{
var caller = new StackTrace()?.GetFrame(1)?.GetMethod()?.Name;
foreach (var logger in loggers)
{
logger.Log(correlationId, message, SeverityLevel.Error, Tags, caller);
}
}
public void Critical(string message)
{
var caller = new StackTrace()?.GetFrame(1)?.GetMethod()?.Name;
foreach (var logger in loggers)
{
logger.Log(correlationId, message, SeverityLevel.Critical, Tags, caller);
}
}
public void Event(string evt, IDictionary<string, double>? metrics) {
var caller = new StackTrace()?.GetFrame(1)?.GetMethod()?.Name;
foreach (var logger in loggers) {
logger.LogEvent(correlationId, evt, Tags, metrics, caller);
}
}
public void Exception(Exception ex, IDictionary<string, double>? metrics) {
var caller = new StackTrace()?.GetFrame(1)?.GetMethod()?.Name;
foreach (var logger in loggers) {
logger.LogException(correlationId, ex, Tags, metrics, caller);
}
}
public void ForceFlush() {
foreach (var logger in loggers) {
logger.Flush();
}
}
}
public class LogTracerFactory {
private List<ILog> loggers;
public LogTracerFactory(List<ILog> loggers) {
this.loggers = loggers;
}
public LogTracer MakeLogTracer(Guid correlationId) {
return new LogTracer(correlationId, this.loggers);
}
}

View File

@ -1,17 +1,41 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Functions.Worker.Configuration;
using Azure.ResourceManager.Storage.Models;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.OneFuzz.Service;
public class Program
{
public static List<ILog> GetLoggers() {
List<ILog> loggers = new List<ILog>();
foreach (var dest in EnvironmentVariables.LogDestinations)
{
loggers.Add(
dest switch
{
LogDestination.AppInsights => new AppInsights(),
LogDestination.Console => new Console(),
_ => throw new Exception(string.Format("Unhandled Log Destination type: {0}", dest)),
}
);
}
return loggers;
}
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices((context, services) =>
services.AddSingleton<LogTracerFactory>(_ => new LogTracerFactory(GetLoggers()))
)
.Build();
host.Run();
}