LearnSpace: full-stack educational whiteboard platform
Node.js/Express backend + vanilla JS frontend. Features: real-time collaborative whiteboard (SSE), multi-page support, LaTeX formulas, shapes/connectors, coordinate systems, number lines, compass, zoom/pan, Catmull-Rom pencil smoothing, ruler/protractor with rotation & resize controls, minimap navigation overlay, auto-measurements, multi-page thumbnails sidebar, PNG export, page templates. Student/teacher workflows: classes, assignments, library, dashboard. Mobile responsive. SQLite (better-sqlite3). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
/* ── Structured logger — JSON in prod, pretty in dev ──────────────────────
|
||||
Usage: logger.info('msg', { key: val })
|
||||
logger.error('msg', { err: e.message })
|
||||
──────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
const LEVELS = { error: 0, warn: 1, info: 2, debug: 3 };
|
||||
|
||||
const COLORS = {
|
||||
error: '\x1b[31m', // red
|
||||
warn: '\x1b[33m', // yellow
|
||||
info: '\x1b[36m', // cyan
|
||||
debug: '\x1b[90m', // gray
|
||||
};
|
||||
const RESET = '\x1b[0m';
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
function _currentLevel() {
|
||||
const env = process.env.LOG_LEVEL;
|
||||
if (env && LEVELS[env] !== undefined) return LEVELS[env];
|
||||
return isProd ? LEVELS.info : LEVELS.debug;
|
||||
}
|
||||
|
||||
function log(level, msg, meta) {
|
||||
if (LEVELS[level] > _currentLevel()) return;
|
||||
|
||||
const ts = new Date().toISOString();
|
||||
|
||||
if (isProd) {
|
||||
/* JSON — one line per entry, parseable by log aggregators */
|
||||
const entry = { level, ts, msg };
|
||||
if (meta && typeof meta === 'object') Object.assign(entry, meta);
|
||||
process.stdout.write(JSON.stringify(entry) + '\n');
|
||||
} else {
|
||||
/* Pretty — coloured label + message + optional meta */
|
||||
const color = COLORS[level] || '';
|
||||
const label = `[${ts}] ${color}${level.toUpperCase().padEnd(5)}${RESET}`;
|
||||
const metaStr = meta && Object.keys(meta).length
|
||||
? ' ' + JSON.stringify(meta, null, 0)
|
||||
: '';
|
||||
process.stdout.write(`${label} ${msg}${metaStr}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
const logger = {
|
||||
error: (msg, meta) => log('error', msg, meta),
|
||||
warn: (msg, meta) => log('warn', msg, meta),
|
||||
info: (msg, meta) => log('info', msg, meta),
|
||||
debug: (msg, meta) => log('debug', msg, meta),
|
||||
|
||||
/* Convenience: log an Error object */
|
||||
exception: (msg, err, meta) => log('error', msg, {
|
||||
err: err?.message,
|
||||
stack: !isProd ? err?.stack : undefined,
|
||||
...meta,
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = logger;
|
||||
Reference in New Issue
Block a user