Skip to content

Commit 3f18621

Browse files
committed
feat: use winston for logging
Signed-off-by: chilir <[email protected]>
1 parent 64c6904 commit 3f18621

19 files changed

+229
-111
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
docs
55
example
66
img
7+
logs
78
node_modules
89
result
910
.editorconfig

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ services:
7171
- ./.env # required
7272
volumes:
7373
- ./config.yaml:/app/config.yaml:ro # required
74+
- ./logs:/app/logs:rw # will be created if not present
7475
- ./.sqlite:/app/.sqlite:rw # will be created if not present
7576

7677
# if you're hosting the model service on the same machine:

example/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
.example_output
1+
.example_output
2+
example_logs

example/compose.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
services:
66
app:
7-
image: ghcr.io/chilir/chat-thyme:nightly
7+
# image: ghcr.io/chilir/chat-thyme:nightly
8+
image: chat-thyme:test-debug
89
command: [ "-c", "/app/config.yaml" ]
910
env_file:
1011
- ../.env
1112
volumes:
1213
- ./config.yaml:/app/config.yaml:ro
14+
- ./example_logs:/app/logs:rw
1315
- ./.example_output:/app/.sqlite:rw
1416

1517
# if you're hosting the model service on the same machine:

src/backend/chat.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import type { Database } from "bun:sqlite";
44
import type Exa from "exa-js";
55
import type OpenAI from "openai";
6+
import winston from "winston";
67
import { getOrInitUserDb, releaseUserDb } from "../db/sqlite";
78
import type {
89
ChatThreadInfo,
@@ -52,7 +53,7 @@ export const extractChoiceContent = async (
5253
chatId: string,
5354
): Promise<ProcessedMessageContent> => {
5455
if (!choices.length) {
55-
console.warn("Received empty choices array in model response");
56+
winston.warn("Received empty choices array in model response");
5657
return {
5758
timestamp: timestamp,
5859
msgContent: "No response was generated",
@@ -177,9 +178,9 @@ export const processUserMessage = async (
177178
systemPrompt,
178179
);
179180

180-
console.debug("\n----------");
181-
console.debug("Current chat messages from DB:");
182-
console.debug(currentChatMessages);
181+
winston.debug("\n----------");
182+
winston.debug("Current chat messages from DB:");
183+
winston.debug(JSON.stringify(currentChatMessages, null, 2));
183184

184185
currentChatMessages.push({ role: "user", content: discordMessageContent });
185186

@@ -211,12 +212,12 @@ export const processUserMessage = async (
211212
extractedChoiceContent.reasoningContent,
212213
);
213214

214-
console.debug("\n----------");
215-
console.debug("Current chat messages:");
216-
console.debug(currentChatMessages);
217-
console.debug("\n----------");
218-
console.debug("Response from model:");
219-
console.debug(formattedModelResponse);
215+
winston.debug("\n----------");
216+
winston.debug("Current chat messages:");
217+
winston.debug(JSON.stringify(currentChatMessages, null, 2));
218+
winston.debug("\n----------");
219+
winston.debug("Response from model:");
220+
winston.debug(JSON.stringify(formattedModelResponse, null, 2));
220221

221222
currentChatMessages.push({
222223
role: "assistant",
@@ -240,7 +241,7 @@ export const processUserMessage = async (
240241
null,
241242
);
242243
} catch (error) {
243-
console.error(
244+
winston.error(
244245
`Error processing user message for ${chatThreadInfo.userId} in chat \
245246
${chatThreadInfo.chatId}:`,
246247
error,

src/backend/llm-service.ts

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { APIError, type OpenAI } from "openai";
44
import pRetry from "p-retry";
5+
import winston from "winston";
56
import type {
67
ChatPrompt,
78
ExpandedChatParameters,
@@ -30,11 +31,11 @@ export const chatWithModel = async (
3031
prompt: ChatPrompt,
3132
options: Partial<ExpandedChatParameters>,
3233
): Promise<OpenAI.Chat.ChatCompletion> => {
33-
console.debug("\n----------");
34-
console.debug("Debug input:");
35-
console.debug(prompt.messages);
36-
console.debug(prompt.messages[0]?.content);
37-
console.debug(options);
34+
winston.debug("\n----------");
35+
winston.debug("Debug input:");
36+
winston.debug(JSON.stringify(prompt.messages, null, 2));
37+
winston.debug(JSON.stringify(prompt.messages[0]?.content, null, 2));
38+
winston.debug(JSON.stringify(options, null, 2));
3839

3940
try {
4041
const response = await pRetry(
@@ -51,17 +52,17 @@ export const chatWithModel = async (
5152
messages: prompt.messages,
5253
...options,
5354
};
54-
console.debug("\n----------");
55-
console.debug("Debug body:");
56-
console.debug(chatBody);
55+
winston.debug("\n----------");
56+
winston.debug("Debug body:");
57+
winston.debug(JSON.stringify(chatBody, null, 2));
5758
const chatResponse = (await modelClient.chat.completions.create(
5859
chatBody,
5960
)) as ExpandedChatResponse;
6061

6162
if (chatResponse.error) {
62-
console.debug("\n----------");
63-
console.debug("Error as part of model response object:");
64-
console.debug(chatResponse.error);
63+
winston.debug("\n----------");
64+
winston.debug("Error as part of model response object:");
65+
winston.debug(JSON.stringify(chatResponse.error, null, 2));
6566

6667
throw new APIError(
6768
chatResponse.error.code,
@@ -89,14 +90,14 @@ export const chatWithModel = async (
8990
);
9091
}
9192

92-
console.debug("\n----------");
93-
console.debug("Error that is not `APIError`:");
94-
console.debug(error);
93+
winston.debug("\n----------");
94+
winston.debug("Error that is not `APIError`:");
95+
winston.debug(JSON.stringify(error, null, 2));
9596

9697
return true; // retry unknown errors
9798
},
9899
onFailedAttempt: (error) => {
99-
console.debug(
100+
winston.debug(
100101
`Chat response attempt ${error.attemptNumber} failed. There are \
101102
${error.retriesLeft} retries left...`,
102103
);
@@ -116,12 +117,12 @@ ${error.retriesLeft} retries left...`,
116117
errorMsg = error instanceof Error ? error.message : `${error}`;
117118
}
118119

119-
console.debug("\n----------");
120-
console.debug(error);
121-
console.debug(`Model response error code: ${errorCode}`);
122-
console.debug(`Model response error message: ${errorMsg}`);
123-
console.debug("Model response error metadata:");
124-
console.debug(errorMetadata);
120+
winston.debug("\n----------");
121+
winston.debug(JSON.stringify(error, null, 2));
122+
winston.debug(`Model response error code: ${errorCode}`);
123+
winston.debug(`Model response error message: ${errorMsg}`);
124+
winston.debug("Model response error metadata:");
125+
winston.debug(JSON.stringify(errorMetadata, null, 2));
125126

126127
if (!errorCode) {
127128
throw new Error(

src/backend/tools.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
processOpenRouterContent,
1717
saveChatMessageToDb,
1818
} from "./utils";
19+
import winston from "winston";
1920

2021
/**
2122
* Processes an Exa search tool call and returns the search results.
@@ -48,20 +49,20 @@ const processExaSearchCall = async (
4849
maxTimeout: 60000,
4950
// TODO: use `shouldRetry` to not retry on 402s (insufficient balance)
5051
onFailedAttempt: (error) => {
51-
console.warn(
52+
winston.warn(
5253
`Search attempt ${error.attemptNumber} failed. ${error.retriesLeft} \
5354
retries left.`,
5455
);
5556
},
5657
},
5758
).catch((error) => {
58-
console.error("Search failed after all retries:", error);
59+
winston.error("Search failed after all retries:", error);
5960
return null;
6061
});
6162

62-
console.debug("\n----------");
63-
console.debug("Exa search results:");
64-
console.debug(searchResults);
63+
winston.debug("\n----------");
64+
winston.debug("Exa search results:");
65+
winston.debug(JSON.stringify(searchResults, null, 2));
6566

6667
return {
6768
role: "tool",
@@ -118,9 +119,9 @@ export const processToolCalls = async (
118119
userId: string,
119120
chatId: string,
120121
): Promise<ProcessedMessageContent> => {
121-
console.debug("\n----------");
122-
console.debug("Tool calls:");
123-
console.debug(toolCalls);
122+
winston.debug("\n----------");
123+
winston.debug("Tool calls:");
124+
winston.debug(JSON.stringify(toolCalls, null, 2));
124125

125126
for (const toolCall of toolCalls) {
126127
if (toolCall.function.name === "exa_search") {
@@ -136,7 +137,7 @@ export const processToolCalls = async (
136137
toolResponse.tool_call_id,
137138
);
138139
} else {
139-
console.warn(`Unknown tool call: ${toolCall.function.name}`);
140+
winston.warn(`Unknown tool call: ${toolCall.function.name}`);
140141
}
141142
}
142143

@@ -171,7 +172,7 @@ export const processToolCalls = async (
171172
}
172173

173174
if (!responseToToolCallResults.choices.length) {
174-
console.warn(
175+
winston.warn(
175176
"Received empty choices array in model response for tool call results",
176177
);
177178
return {

src/backend/utils.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Database } from "bun:sqlite";
44
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
55
import type OpenAI from "openai";
66
import tmp from "tmp";
7+
import winston from "winston";
78
import type {
89
DbChatMessageToSave,
910
ExpandedChatCompletionMessage,
@@ -18,6 +19,27 @@ import {
1819
} from "./utils";
1920

2021
tmp.setGracefulCleanup();
22+
const errorPrintfFormat = winston.format.printf(
23+
({ level, message, timestamp, stack, ...metadata }) => {
24+
let logMsg = `[${timestamp}] ${level}: ${message}`;
25+
if (stack) {
26+
logMsg += `\nStack trace:\n${stack}`; // Add stack trace if available
27+
}
28+
if (Object.keys(metadata).length > 0) {
29+
logMsg += `\nMetadata: ${JSON.stringify(metadata, null, 2)}`; // Add metadata as JSON
30+
}
31+
return logMsg;
32+
},
33+
);
34+
winston.add(
35+
new winston.transports.Console({
36+
format: winston.format.combine(
37+
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
38+
winston.format.colorize(),
39+
errorPrintfFormat, // Use the new format
40+
),
41+
}),
42+
);
2143

2244
const systemRole = "system";
2345
const testSystemPrompt = "You are a helpful assistant.";

src/backend/utils.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import type { Database } from "bun:sqlite";
44
import type OpenAI from "openai";
5+
import winston from "winston";
56
import type {
67
DbChatMessageToSave,
78
ExpandedChatCompletionMessage,
@@ -31,7 +32,7 @@ export const parseDbRow = (
3132
row.content as string,
3233
) as OpenAI.ChatCompletionContentPart[];
3334
} catch (error) {
34-
console.error(
35+
winston.error(
3536
`Error parsing tool call (id: ${row.tool_call_id}) content from \
3637
database. Raw JSON string: ${row.content}:`,
3738
error,
@@ -53,7 +54,7 @@ database. Raw JSON string: ${row.content}:`,
5354
row.tool_calls as string,
5455
) as OpenAI.ChatCompletionMessageToolCall[];
5556
} catch (error) {
56-
console.error(
57+
winston.error(
5758
`Error parsing tool calls from database. Raw JSON string: \
5859
${row.tool_calls}:`,
5960
error,
@@ -105,7 +106,7 @@ export const getChatHistoryFromDb = async (
105106
.all(chatId) as DbChatMessageToSave[]
106107
).map(parseDbRow) as OpenAI.ChatCompletionMessageParam[];
107108
} catch (error) {
108-
console.error(
109+
winston.error(
109110
`Error getting chat history from database for ${userId} in chat \
110111
${chatId}:`,
111112
error,
@@ -251,7 +252,7 @@ export const saveChatMessageToDb = async (
251252
],
252253
);
253254
} catch (error) {
254-
console.error(
255+
winston.error(
255256
`Error saving chat message to database for ${userId} in chat ${chatId}:`,
256257
error,
257258
);

src/config/parse.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,33 @@ import { beforeEach, describe, expect, it } from "bun:test";
44
import fs from "node:fs";
55
import path from "node:path";
66
import tmp from "tmp";
7+
import winston from "winston";
78
import { ZodError } from "zod";
89
import { parseConfig } from "./parse";
910
import { defaultAppConfig } from "./schema";
1011

1112
tmp.setGracefulCleanup();
13+
const errorPrintfFormat = winston.format.printf(
14+
({ level, message, timestamp, stack, ...metadata }) => {
15+
let logMsg = `[${timestamp}] ${level}: ${message}`;
16+
if (stack) {
17+
logMsg += `\nStack trace:\n${stack}`; // Add stack trace if available
18+
}
19+
if (Object.keys(metadata).length > 0) {
20+
logMsg += `\nMetadata: ${JSON.stringify(metadata, null, 2)}`; // Add metadata as JSON
21+
}
22+
return logMsg;
23+
},
24+
);
25+
winston.add(
26+
new winston.transports.Console({
27+
format: winston.format.combine(
28+
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
29+
winston.format.colorize(),
30+
errorPrintfFormat
31+
),
32+
}),
33+
);
1234

1335
describe("Configuration Parsing and Loading", () => {
1436
const originalEnv = { ...process.env };

0 commit comments

Comments
 (0)