+2
-1
backend/src/db/database.ts
+2
-1
backend/src/db/database.ts
···
6
6
7
7
import { drizzle } from "drizzle-orm/libsql";
8
8
import { Config } from "../config.ts";
9
+
import Logger from "../logger.js";
9
10
10
11
const config = Config.getInstance();
11
12
const dbname = config.get("database.name");
···
23
24
try {
24
25
Database.instance = new Database();
25
26
} catch (e) {
26
-
console.error(e);
27
+
Logger.error(e);
27
28
process.exit(1);
28
29
}
29
30
}
+38
backend/src/logger.ts
+38
backend/src/logger.ts
···
1
+
/*
2
+
* clippr: a social bookmarking service for the AT Protocol
3
+
* Copyright (c) 2025 clippr contributors.
4
+
* SPDX-License-Identifier: AGPL-3.0-only
5
+
*/
6
+
7
+
import { createLogger, format, transports } from "winston";
8
+
9
+
// TODO: I can't seem to actually get the config setting for the log level yet.
10
+
const loglevel: string = "debug";
11
+
12
+
const Logger = createLogger({
13
+
level: loglevel,
14
+
transports: [
15
+
new transports.Console({
16
+
format: format.combine(
17
+
format.colorize(),
18
+
format.timestamp(),
19
+
format.printf(({ level, message, timestamp }) => {
20
+
return `[${timestamp}] ${level}: ${message}`;
21
+
}),
22
+
),
23
+
}),
24
+
new transports.File({
25
+
dirname: "logs",
26
+
filename: "clippr-error.log",
27
+
level: "error",
28
+
}),
29
+
new transports.File({
30
+
dirname: "logs",
31
+
filename: "clippr-combined.log",
32
+
}),
33
+
],
34
+
format: format.combine(format.timestamp(), format.json()),
35
+
defaultMeta: { service: "clippr-backend" },
36
+
});
37
+
38
+
export default Logger;
+11
-9
backend/src/main.ts
+11
-9
backend/src/main.ts
···
9
9
import { readFromFirehose, startFirehose, stopFirehose } from "./network/jetstream.ts";
10
10
import app from "./server.ts";
11
11
import { Database } from "./db/database.js";
12
+
import Logger from "./logger.js";
12
13
13
-
function main() {
14
-
console.log("clippr-BE starting...");
14
+
async function main() {
15
+
let logger = Logger;
16
+
logger.info("clippr-BE starting...");
15
17
16
-
console.log("initializing config");
17
-
const config: Config = Config.getInstance(); // Get config from config.toml
18
+
logger.info("initializing config");
19
+
const config = Config.getInstance();
18
20
19
-
console.log("initializing database");
21
+
logger.info("initializing database");
20
22
Database.getInstance();
21
23
22
-
console.log("initializing firehose connection");
24
+
logger.info("initializing firehose connection");
23
25
startFirehose();
24
26
readFromFirehose();
25
27
···
29
31
fetch: app.fetch,
30
32
});
31
33
32
-
console.log(
34
+
logger.info(
33
35
`server started at http://${config.get("hostname")}:${config.get("port")}`,
34
36
);
35
37
···
40
42
process.once("SIGTERM", () => gracefulShutdown("SIGTERM"));
41
43
42
44
function gracefulShutdown(signal: string) {
43
-
console.log(`\nreceived ${signal}, shutting down...`);
45
+
logger.info(`\nreceived ${signal}, shutting down...`);
44
46
stopFirehose();
45
47
server.close();
46
-
console.log("server shut down, bye!");
48
+
logger.info("server shut down, bye!");
47
49
process.exit(0);
48
50
}
49
51
}
+5
-4
backend/src/network/commit.ts
+5
-4
backend/src/network/commit.ts
···
10
10
import { is } from "@atcute/lexicons";
11
11
import { SocialClipprFeedClip, SocialClipprFeedTag } from "@clipprjs/lexicons";
12
12
import type { At } from "@atcute/client/lexicons";
13
+
import Logger from "../logger.js";
13
14
14
15
const db = Database.getInstance().getDb();
15
16
···
27
28
if (event.commit.operation !== "create") return; // We currently do not handle these.
28
29
29
30
if (!is(SocialClipprFeedClip.mainSchema, event.commit.record)) {
30
-
console.log("invalid clip", event.commit.record);
31
+
Logger.verbose("Invalid clip", event.commit.record);
31
32
return;
32
33
}
33
34
···
60
61
languages: record.languages,
61
62
});
62
63
63
-
console.log("inserted new clip");
64
+
Logger.verbose("Indexed new clip:", event.did, event.commit.rkey);
64
65
}
65
66
66
67
export async function handleTag(event: CommitEvent<`social.clippr.${string}`>) {
67
68
if (event.commit.operation !== "create") return; // We currently do not handle these.
68
69
69
70
if (!is(SocialClipprFeedTag.mainSchema, event.commit.record)) {
70
-
console.log("invalid tag", event.commit.record);
71
+
Logger.verbose("Invalid tag", event.commit.record);
71
72
return;
72
73
}
73
74
···
87
88
color: record.color,
88
89
});
89
90
90
-
console.log("inserted new tag");
91
+
Logger.verbose("Indexed new tag:", event.did, event.commit.rkey);
91
92
}
+5
-4
backend/src/network/jetstream.ts
+5
-4
backend/src/network/jetstream.ts
···
7
7
import { Jetstream } from "@skyware/jetstream";
8
8
import { Config } from "../config.ts";
9
9
import { handleClip, handleTag } from "./commit.js";
10
+
import Logger from "../logger.js";
10
11
11
12
const config = Config.getInstance();
12
13
const hostname = config.get("network.firehose");
···
34
35
handleTag(e);
35
36
break;
36
37
default:
37
-
console.log(
38
+
Logger.debug(
38
39
`commit for ${e.commit.collection} is not relevant, dropping`,
39
40
);
40
41
break;
···
42
43
});
43
44
44
45
jetstream.on("account", (e) => {
45
-
console.log("account update", e.account.did);
46
+
Logger.debug(`Received account update for ${e.account.did}`);
46
47
});
47
48
48
49
jetstream.on("identity", (e) => {
49
-
console.log("identity update", e.identity.did);
50
+
Logger.debug(`Received identity update for ${e.identity.did}`);
50
51
});
51
52
52
53
jetstream.on("error", (e) => {
53
-
console.log(e);
54
+
Logger.warn(e);
54
55
});
55
56
}
+7
backend/src/server.ts
+7
backend/src/server.ts
···
6
6
7
7
import { Hono } from "hono";
8
8
import misc from "./routes/misc.ts";
9
+
import Logger from "./logger.js";
10
+
import { logger } from "hono/logger";
11
+
12
+
export function winstonLogger(message: string, ...rest: unknown[]) {
13
+
Logger.http(message, ...rest);
14
+
}
9
15
10
16
const app = new Hono();
17
+
app.use(logger(winstonLogger));
11
18
12
19
// Link all routes up
13
20
app.route("/", misc);