Repository: microsoft/onefuzz
File: src/ApiService/ApiService/Log.cs (C#)

Code before:
        if (metrics is not null) {
            copyMetrics = new(metrics);
        }

        _telemetryClient.TrackEvent(evt.ToString(), properties: copyTags, metrics: copyMetrics);
    }

    public void LogException(Guid correlationId, Exception ex, LogStringHandler message, IReadOnlyDictionary<string, string> tags, IReadOnlyDictionary<string, double>? metrics, string? caller) {
        Dictionary<string, string> copyTags = new(tags);
        copyTags["CorrelationId"] = correlationId.ToString();
        if (caller is not null) copyTags["CalledBy"] = caller;

        if (message.Tags is not null) {
            foreach (var kv in message.Tags) {
                copyTags[kv.Key] = kv.Value;
            }
        }

        Dictionary<string, double>? copyMetrics = null;
        if (metrics is not null) {
            copyMetrics = new(metrics);
        }
        _telemetryClient.TrackException(ex, copyTags, copyMetrics);
        Log(correlationId, $"[{message}] {ex.Message}", SeverityLevel.Error, tags, caller);
    }

    public void Flush() {
        _telemetryClient.Flush();
    }
}

//TODO: Should we write errors and Exception to std err ? 
class Console : ILog {

    private static string DictToString<T>(IReadOnlyDictionary<string, T>? d) {
        if (d is null) {
            return string.Empty;
        } else {
            return string.Join("", d);
        }
    }

    private static void LogTags(Guid correlationId, IReadOnlyDictionary<string, string> tags) {
        var ts = DictToString(tags);
        if (!string.IsNullOrEmpty(ts)) {
            System.Console.WriteLine($"[{correlationId}] Tags:{ts}");
        }
    }

    private static void LogMetrics(Guid correlationId, IReadOnlyDictionary<string, double>? metrics) {
        var ms = DictToString(metrics);

Review comment (suggestion):
the callstack made me wonder if it was `ex` that was `null` here

Code after:

        Dictionary<string, double>? copyMetrics = null;
        if (metrics is not null) {
            copyMetrics = new(metrics);
        }

        _telemetryClient.TrackEvent(evt.ToString(), properties: copyTags, metrics: copyMetrics);
    }

    public void LogException(Guid correlationId, Exception ex, LogStringHandler message, IReadOnlyDictionary<string, string> tags, IReadOnlyDictionary<string, double>? metrics, string? caller) {
        Dictionary<string, string> copyTags = new(tags);
        copyTags["CorrelationId"] = correlationId.ToString();
        if (caller is not null) copyTags["CalledBy"] = caller;

        if (message.Tags is not null) {
            foreach (var kv in message.Tags) {
                copyTags[kv.Key] = kv.Value;
            }
        }

        Dictionary<string, double>? copyMetrics = null;
        if (metrics is not null) {
            copyMetrics = new(metrics);
        }
        _telemetryClient.TrackException(ex, copyTags, copyMetrics);
        Log(correlationId, $"[{message}] {ex.Message}", SeverityLevel.Error, tags, caller);
    }

    public void Flush() {
        _telemetryClient.Flush();
    }
}

//TODO: Should we write errors and Exception to std err ? 
class Console : ILog {

    private static string DictToString<T>(IReadOnlyDictionary<string, T>? d) {
        if (d is null) {
            return string.Empty;
        } else {
            return string.Join("", d);
        }
    }

    private static void LogTags(Guid correlationId, IReadOnlyDictionary<string, string> tags) {
        var ts = DictToString(tags);
        if (!string.IsNullOrEmpty(ts)) {
            System.Console.WriteLine($"[{correlationId}] Tags:{ts}");
        }
    }