diff --git a/go/extractor/diagnostics/diagnostics.go b/go/extractor/diagnostics/diagnostics.go index 4a49347f0f7..a825bf86288 100644 --- a/go/extractor/diagnostics/diagnostics.go +++ b/go/extractor/diagnostics/diagnostics.go @@ -3,7 +3,7 @@ package diagnostics import ( "encoding/json" "fmt" - "log" + "log/slog" "os" "strings" "time" @@ -56,19 +56,62 @@ type diagnostic struct { var diagnosticsEmitted, diagnosticsLimit uint = 0, 100 var noDiagnosticDirPrinted bool = false -func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) { +type DiagnosticsWriter interface { + WriteDiagnostic(d diagnostic) +} + +type FileDiagnosticsWriter struct { + diagnosticDir string +} + +func (writer *FileDiagnosticsWriter) WriteDiagnostic(d diagnostic) { + content, err := json.Marshal(d) + if err != nil { + slog.Error("Failed to encode diagnostic as JSON", slog.Any("err", err)) + return + } + + targetFile, err := os.CreateTemp(writer.diagnosticDir, "go-extractor.*.json") + if err != nil { + slog.Error("Failed to create diagnostic file", slog.Any("err", err)) + return + } + defer func() { + if err := targetFile.Close(); err != nil { + slog.Error("Failed to close diagnostic file", slog.Any("err", err)) + } + }() + + _, err = targetFile.Write(content) + if err != nil { + slog.Error("Failed to write to diagnostic file", slog.Any("err", err)) + } +} + +var DefaultWriter *FileDiagnosticsWriter = nil + +func NewFileDiagnosticsWriter() *FileDiagnosticsWriter { + diagnosticDir := os.Getenv("CODEQL_EXTRACTOR_GO_DIAGNOSTIC_DIR") + if diagnosticDir == "" { + if !noDiagnosticDirPrinted { + slog.Warn("No diagnostic directory set, so not emitting diagnostics") + noDiagnosticDirPrinted = true + } + return nil + } + + return &FileDiagnosticsWriter{diagnosticDir} +} + +func init() { + DefaultWriter = NewFileDiagnosticsWriter() +} + +// Emits a diagnostic using the specified `DiagnosticsWriter`. +func emitDiagnosticTo(writer DiagnosticsWriter, sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) { if diagnosticsEmitted < diagnosticsLimit { diagnosticsEmitted += 1 - diagnosticDir := os.Getenv("CODEQL_EXTRACTOR_GO_DIAGNOSTIC_DIR") - if diagnosticDir == "" { - if !noDiagnosticDirPrinted { - log.Println("No diagnostic directory set, so not emitting diagnostic") - noDiagnosticDirPrinted = true - } - return - } - timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000") + "Z" var d diagnostic @@ -93,33 +136,18 @@ func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagn } } - content, err := json.Marshal(d) - if err != nil { - log.Println(err) - return - } - - targetFile, err := os.CreateTemp(diagnosticDir, "go-extractor.*.json") - if err != nil { - log.Println("Failed to create diagnostic file: ") - log.Println(err) - return - } - defer func() { - if err := targetFile.Close(); err != nil { - log.Println("Failed to close diagnostic file:") - log.Println(err) - } - }() - - _, err = targetFile.Write(content) - if err != nil { - log.Println("Failed to write to diagnostic file: ") - log.Println(err) - } + writer.WriteDiagnostic(d) } } +// Emits a diagnostic using the default `DiagnosticsWriter`. +func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) { + if DefaultWriter == nil { + return + } + emitDiagnosticTo(DefaultWriter, sourceid, sourcename, markdownMessage, severity, visibility, location) +} + func EmitPackageDifferentOSArchitecture(pkgPath string) { emitDiagnostic( "go/autobuilder/package-different-os-architecture",