Skip to content

Commit 708b985

Browse files
committed
feat: surface logs as errors
1 parent 46df927 commit 708b985

File tree

6 files changed

+106
-22
lines changed

6 files changed

+106
-22
lines changed

preview/apitypes/apitypes.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package apitypes
22

33
import (
4+
"time"
5+
46
"github.com/coder/preview"
57
"github.com/coder/preview/types"
68
)
@@ -11,7 +13,16 @@ type PreviewOutput struct {
1113
// ParserLogs are trivy logs that occur during parsing the
1214
// Terraform files. This is useful for debugging issues with the
1315
// invalid terraform syntax.
14-
ParserLogs string `json:"parser_logs,omitempty"`
16+
ParserLogs []ParserLog `json:"parser_logs,omitempty"`
17+
}
18+
19+
type ParserLog struct {
20+
Time time.Time `json:"time"`
21+
Level string `json:"level"`
22+
Message string `json:"msg"`
23+
Prefix string `json:"prefix"`
24+
Module string `json:"root"`
25+
Err string `json:"err"`
1526
}
1627

1728
type NullHCLString = types.NullHCLString

preview/main.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
package main
44

55
import (
6-
"bytes"
76
"context"
87
"encoding/json"
98
"fmt"
109
"io/fs"
1110
"log/slog"
1211
"path/filepath"
12+
"sync"
1313
"syscall/js"
14+
"time"
1415

1516
"github.com/hashicorp/hcl/v2"
1617
"github.com/spf13/afero"
@@ -34,13 +35,13 @@ func main() {
3435
}
3536

3637
func tfpreview(this js.Value, p []js.Value) (output any) {
37-
var buf bytes.Buffer
38+
l := NewLogger()
3839
defer func() {
3940
// Return a panic as a diagnostic if one occurs.
4041
if r := recover(); r != nil {
4142
data, _ := json.Marshal(apitypes.PreviewOutput{
4243
Output: nil,
43-
ParserLogs: buf.String(),
44+
ParserLogs: l.entries,
4445
Diags: types.Diagnostics{
4546
{
4647
Severity: hcl.DiagError,
@@ -60,10 +61,8 @@ func tfpreview(this js.Value, p []js.Value) (output any) {
6061
return err
6162
}
6263

63-
logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{
64-
AddSource: false,
65-
Level: slog.LevelDebug,
66-
}))
64+
handler := slog.NewJSONHandler(l, nil)
65+
logger := slog.New(handler)
6766
pOutput, diags := preview.Preview(context.Background(), preview.Input{
6867
PlanJSONPath: "",
6968
PlanJSON: nil,
@@ -75,7 +74,7 @@ func tfpreview(this js.Value, p []js.Value) (output any) {
7574
data, _ := json.Marshal(apitypes.PreviewOutput{
7675
Output: pOutput,
7776
Diags: types.Diagnostics(diags),
78-
ParserLogs: buf.String(),
77+
ParserLogs: l.entries,
7978
})
8079

8180
return js.ValueOf(string(data))
@@ -118,3 +117,31 @@ func loadTree(mem afero.Fs, fileTree map[string]any, path ...string) {
118117
}
119118
}
120119
}
120+
121+
type Logger struct {
122+
mu sync.Mutex
123+
entries []apitypes.ParserLog
124+
}
125+
126+
func NewLogger() *Logger {
127+
return &Logger{
128+
entries: make([]apitypes.ParserLog, 0),
129+
}
130+
}
131+
132+
func (l *Logger) Write(p []byte) (n int, err error) {
133+
var entry apitypes.ParserLog
134+
if err := json.Unmarshal(p, &entry); err != nil {
135+
entry = apitypes.ParserLog{
136+
Time: time.Now(),
137+
Level: "unknown",
138+
Message: string(p),
139+
}
140+
}
141+
142+
l.mu.Lock()
143+
l.entries = append(l.entries, entry)
144+
l.mu.Unlock()
145+
146+
return len(p), nil
147+
}

public/build/preview.wasm

601 Bytes
Binary file not shown.

src/Preview.tsx

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,36 @@ export const Preview: FC = () => {
104104
>
105105
{output ? (
106106
<div className="flex flex-col gap-4">
107-
<p className=" w-fit break-all text-content-primary">
108-
{JSON.stringify(output.output?.Parameters, null, 2)}
109-
</p>
107+
<div className="flex w-fit flex-col gap-3">
108+
<p className="font-semibold text-content-primary text-xl">
109+
Parameters
110+
</p>
111+
<p className=" w-fit break-all text-content-primary">
112+
{JSON.stringify(output.output?.Parameters, null, 2)}
113+
</p>
114+
</div>
110115

111-
<p className=" w-fit break-all text-content-primary">
112-
{JSON.stringify(output.diags, null, 2)}
113-
</p>
116+
<div className="flex w-fit flex-col gap-3">
117+
<p className="font-semibold text-content-primary text-xl">
118+
Diagnostics
119+
</p>
120+
<p className=" w-fit break-all text-content-primary">
121+
{JSON.stringify(output.diags, null, 2)}
122+
</p>
123+
</div>
114124

115-
<p className=" w-fit break-all text-content-primary">
116-
{output.parser_logs}
117-
</p>
125+
<div className="flex w-fit flex-col gap-3">
126+
<p className="font-semibold text-content-primary text-xl">
127+
Logs
128+
</p>
129+
<div className="flex flex-col gap-2">
130+
{output.parser_logs?.map((log, index) => (
131+
<p key={index} className="-indent-4 pl-4">
132+
{log.time} {log.level}: {log.msg} - {log.err}
133+
</p>
134+
))}
135+
</div>
136+
</div>
118137
</div>
119138
) : (
120139
<PreviewEmptyState />

src/diagnostics.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { FriendlyDiagnostic, Parameter, PreviewOutput } from "./gen/types";
1+
import type {
2+
FriendlyDiagnostic,
3+
Parameter,
4+
ParserLog,
5+
PreviewOutput,
6+
} from "./gen/types";
27

38
type FriendlyDiagnosticWithoutKind = Omit<FriendlyDiagnostic, "extra">;
49

@@ -24,15 +29,15 @@ export const outputToDiagnostics = (output: PreviewOutput): Diagnostic[] => {
2429
const parameterDiags = (output.output?.Parameters ?? []).flatMap(
2530
parameterToDiagnostics,
2631
);
27-
2832
const topLevelDiags: TopLevelDiagnostic[] = output.diags
2933
.filter((d) => d !== null)
3034
.map((d) => ({
3135
kind: "top-level",
3236
...d,
3337
}));
38+
const diagsFromLogs = logsToDiagnostics(output.parser_logs ?? []);
3439

35-
return [...topLevelDiags, ...parameterDiags];
40+
return [...diagsFromLogs, ...topLevelDiags, ...parameterDiags];
3641
};
3742

3843
const parameterToDiagnostics = (parameter: Parameter): ParameterDiagnostic[] =>
@@ -43,3 +48,15 @@ const parameterToDiagnostics = (parameter: Parameter): ParameterDiagnostic[] =>
4348
parameterName: parameter.name,
4449
...d,
4550
}));
51+
52+
const logsToDiagnostics = (logs: ParserLog[]): TopLevelDiagnostic[] =>
53+
logs
54+
// Non-error level logs seem to either be redundant with given diagnostics or
55+
// not useful so for now we filter them out
56+
.filter((log) => log.level === "ERROR")
57+
.map((log) => ({
58+
kind: "top-level",
59+
severity: log.level,
60+
summary: log.msg,
61+
detail: log.err,
62+
}));

src/gen/types.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,21 @@ export interface ParameterValidation {
101101
validation_monotonic: string | null;
102102
}
103103

104+
// From apitypes/apitypes.go
105+
export interface ParserLog {
106+
time: string;
107+
level: string;
108+
msg: string;
109+
prefix: string;
110+
root: string;
111+
err: string;
112+
}
113+
104114
// From apitypes/apitypes.go
105115
export interface PreviewOutput {
106116
output: Output | null;
107117
diags: Diagnostics;
108-
parser_logs?: string;
118+
parser_logs?: ParserLog[];
109119
}
110120

111121
// From types/tags.go

0 commit comments

Comments
 (0)