{"id":33311719,"url":"https://github.com/kitschpatrol/lognow","last_synced_at":"2026-05-16T20:02:36.626Z","repository":{"id":322319240,"uuid":"1086898284","full_name":"kitschpatrol/lognow","owner":"kitschpatrol","description":"Quick and clean universal logging.","archived":false,"fork":false,"pushed_at":"2026-04-02T19:13:28.000Z","size":1623,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-03T06:36:30.529Z","etag":null,"topics":["electron","log","logging","loglayer","npm-package"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kitschpatrol.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-31T04:26:01.000Z","updated_at":"2026-04-02T19:13:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kitschpatrol/lognow","commit_stats":null,"previous_names":["kitschpatrol/lognow"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/kitschpatrol/lognow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitschpatrol%2Flognow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitschpatrol%2Flognow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitschpatrol%2Flognow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitschpatrol%2Flognow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kitschpatrol","download_url":"https://codeload.github.com/kitschpatrol/lognow/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitschpatrol%2Flognow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31483380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T17:22:55.647Z","status":"ssl_error","status_checked_at":"2026-04-06T17:22:54.741Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["electron","log","logging","loglayer","npm-package"],"created_at":"2025-11-19T05:04:30.531Z","updated_at":"2026-05-16T20:02:36.619Z","avatar_url":"https://github.com/kitschpatrol.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- title --\u003e\n\n# lognow\n\n\u003c!-- /title --\u003e\n\n\u003c!-- badges --\u003e\n\n[![NPM Package lognow](https://img.shields.io/npm/v/lognow.svg)](https://npmjs.com/package/lognow)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/license/mit/)\n[![CI](https://github.com/kitschpatrol/lognow/actions/workflows/ci.yml/badge.svg)](https://github.com/kitschpatrol/lognow/actions/workflows/ci.yml)\n\n\u003c!-- /badges --\u003e\n\n\u003c!-- short-description --\u003e\n\n**Quick and clean universal logging.**\n\n\u003c!-- /short-description --\u003e\n\n## Overview\n\n\u003e [!WARNING]\n\u003e\n\u003e Lognow is under development. It should not be considered suitable for general use until a 1.0 release.\n\nSomehow, no single logging library out there quite worked for my purposes... so it's come to this.\n\nLognow provides a handful of helpers and turn-key configurations for (my) typical logging needs. It's a thin wrapper over the [LogLayer](https://loglayer.dev) project, allowing its use in a consistent, pre-configured, and unobtrusive way.\n\nIt provides:\n\n- Pretty console logs by default with a reasonable amount of metadata, colorization, object formatting, error handling, etc.\n- Rotating JSONL file logs enabled via a single option flag.\n- A plausible strategy for log dependency injection in library projects and integration with other logging systems.\n- A universal / isomorphic implementation with support for Node.js, web browsers, and Electron.\n\nIt's probably most similar aesthetically to [tslog](https://tslog.js.org), but with the extra interoperability abstractions provided by [LogLayer](https://loglayer.dev), and integrated Electron support a la [electron-log](https://github.com/megahertz/electron-log).\n\nThis library is not designed to be infinitely configurable or extensible. Instead, it's designed to cover 95% of my use-cases with a single import, and gets to 99% with a few lines of configuration.\nFor the remaining 1% of my logging needs, it makes more sense to just work with LogLayer directly.\n\n## Getting started\n\n### Dependencies\n\nNode 20.19.0+, or any recent web browser.\n\n### Installation\n\n#### Local\n\n```sh\nnpm install lognow\n```\n\n#### CDN\n\nA bundled and minified ES module is available on CDNs:\n\n```ts\nimport { log } from 'https://cdn.jsdelivr.net/npm/lognow'\n```\n\nor\n\n```ts\nimport { log } from 'https://unpkg.com/lognow'\n```\n\n### Quick Start\n\n```ts\nimport { log } from 'lognow'\n\n// Simple logging\nlog.info('Hello, world!')\n\n// With metadata\nlog.withMetadata({ action: 'login', userId: '123' }).info('User logged in')\n\n// With persistent context\nlog.withContext({ requestId: 'req-456' })\nlog.info('Processing request')\nlog.info('Request completed')\n// Both logs include the requestId context\n\n// Different log levels\nlog.debug('Debug message')\nlog.warn('Warning message')\nlog.error('Error message')\n```\n\n## Usage\n\n### Basic\n\nMost of the time, it makes sense to just import the default log instance:\n\n```ts\nimport { log } from 'lognow'\n\nlog.info('What hath God wrought?')\n```\n\nBy default, you'll get a timestamped pretty log in the console or terminal:\n\n```txt\n12:47:56.394 INFO | What hath God wrought?\n```\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e The log instance's interface is a bit different than a typical `Console` object — it's a LogLayer `ILogLayer` instance, so instead of passing objects and metadata directly to the logging method, additional methods are chained together to separate message strings from metadata or context objects.\n\nA quick example:\n\n```ts\nimport { log } from 'lognow'\n\nlog.withMetadata({ energy: 2, mood: 3 }).info('vibe check')\n```\n\n```txt\n15:09:21.011 INFO | vibe check\n{ mood: 3, energy: 2 }\n```\n\nThis is a bit of extra work, but it disambiguates your logging intent in such a way that future interoperability with different logging targets is assured.\n\nSee the [LogLayer docs](https://loglayer.dev) for a full description of the interface.\n\n### Log Levels\n\nLognow supports six log levels, from least to most severe:\n\n```ts\nimport { log } from 'lognow'\n\nlog.trace('Detailed debugging information')\nlog.debug('General debugging information')\nlog.info('Informational messages')\nlog.warn('Warning messages')\nlog.error('Error messages')\nlog.fatal('Fatal error messages')\n```\n\nBy default, only `info` and above are displayed. Use the `verbose` option to show all levels (see [Configuration](#configuration) below).\n\n### Logging Errors\n\nErrors are automatically formatted with stack traces:\n\n```ts\nimport { log } from 'lognow'\n\ntry {\n  throw new Error('Something went wrong!')\n} catch (error) {\n  log.error('Failed to process request', error)\n}\n```\n\n```txt\n15:09:21.011 ERROR | Failed to process request\nError: Something went wrong!\n    at Object.\u003canonymous\u003e (/path/to/file.ts:4:9)\n    ...\n```\n\n### Context vs Metadata\n\nThe underlying LogLayer library provides two basic ways to attach data to your logs:\n\n- **`withMetadata()`** - Ephemeral: Attaches data to a single log entry\n- **`withContext()`** - Persistent: Adds context to the logger instance itself, which persists across all subsequent log calls on that instance\n\n```ts\nimport { log } from 'lognow'\n\n// Metadata: attached to one log only\nlog.withMetadata({ requestId: '123' }).info('Processing request')\nlog.info('This log has no metadata')\n\n// Context: modifies the logger instance\nlog.withContext({ requestId: '123', userId: '456' })\nlog.info('Starting request')\nlog.info('Request completed')\n// Both logs will include the context (requestId and userId)\n```\n\n### Multiple Messages\n\nYou can pass multiple messages to a single log call, but they must be a primitive type (objects need to be passed as metadata or context):\n\n```ts\nimport { log } from 'lognow'\n\nlog.info('User logged in', 'Session started', 'Welcome email sent')\n```\n\n```txt\n15:09:21.011 INFO | User logged in Session started Welcome email sent\n```\n\n### Options\n\nLognow has just five options:\n\n#### `name`\n\nSet the name of the logger, which is used as a prefix in messages logged to a console, and as extra metadata in logs written to files.\n\nThe default behavior depends on your runtime context:\n\n- Browser projects default to undefined.\n- Node projects with a `package.json` will default to the package name.\n- Electron projects default to `\"Main\"` for the main process and `\"Renderer\"` for the renderer process.\n\n#### `logToConsole`\n\nSet to `true` or `false` to enable or disable pretty console logging.\n\nDefaults to `true`.\n\nAlternately, set to any Console- or Stream-like log target, or a partial `PrettyBasicTransportConfig` object to override the default configuration. (If you don't define a log target explicitly, `process.stderr` is used in Node.js and `console` is used in the browser.)\n\n#### `logJsonToFile`\n\nSet to `true` or `false` to enable or disable file logging. When enabled, logs are written as newline-delimited JSON objects (JSONL) to disk.\n\nDefaults to `false`.\n\nLogs are stored in the platform-standard location. The files are gzipped and rotated daily, and are never removed.\n\nComplex metadata or context objects not natively representable within the JSON specification are serialized on a best-effort basis, with emphasis on being human-readable rather than being perfectly reconstructible as JavaScript.\n\nAlternately, pass a path to a directory to log to the location of your choosing, or a partial `JsonFileTransportConfig` object to override the default configuration.\n\n#### `logJsonToConsole`\n\nSet to `true` or `false` to enable or disable JSON console logging. When enabled, logs are written as JSON strings to the console.\n\nDefaults to `false`.\n\nAlternately, set to any Console- or Stream-like log target, or a partial `JsonBasicTransportConfig` object to override the default configuration.\n\n#### `verbose`\n\nThis is just a shortcut for setting the log level.\n\nIf `true`, all logs are shown regardless of level. If `false`, only `info` and higher logs are shown.\n\nDefaults to `false`.\n\n| Level   | Priority | Verbose: False | Verbose: True |\n| ------- | -------- | -------------- | ------------- |\n| `trace` | 10       | ❌             | ✅            |\n| `debug` | 20       | ❌             | ✅            |\n| `info`  | 30       | ✅             | ✅            |\n| `warn`  | 40       | ✅             | ✅            |\n| `error` | 50       | ✅             | ✅            |\n| `fatal` | 60       | ✅             | ✅            |\n\n### Configuration\n\n#### Configuring the default log instance\n\nUse `setDefaultLogOptions()` to set options on the default log instance.\n\n```ts\nimport { log, setDefaultLogOptions } from 'lognow'\n\nsetDefaultLogOptions({\n  logJsonToConsole: true,\n  logToConsole: false,\n  name: 'example',\n})\n\nlog.withMetadata({ energy: 2, mood: 3 }).info('vibe check')\n```\n\nWill log:\n\n```txt\n{\"level\":\"info\",\"messages\":[\"vibe check\"],\"metadata\":{\"energy\":2,\"mood\":3},\"name\":\"example\",\"timestamp\":\"2025-10-25T19:34:14.754Z\"}\n```\n\n#### Configuring new instances\n\nUse `createLogger()` to set options on a new custom logger instance. Defaults are assumed for any undefined options.\n\n```ts\nimport { createLogger } from 'lognow'\n\nconst customLog = createLogger({\n  name: 'custom',\n})\n\ncustomLog.info('hello')\n```\n\nWill log:\n\n```txt\n15:38:50.138 INFO  [custom] hello\n```\n\nNote that only the default log instance's configuration is mutable (via `setDefaultLogOptions()`). Custom logs should be recreated with `createLogger()` if you need to change configuration.\n\n#### Environment configuration\n\nIn Node.js-like environments, Lognow respects several environment variables:\n\n- [`NO_COLOR`](https://no-color.org/) - Disables colorization in console output\n- [`FORCE_COLOR`](https://force-color.org/) - Enables colorization (takes precedence over `NO_COLOR` if both are set)\n- `DEBUG` - Enables verbose logging (equivalent to `verbose: true`)\n\nThese environment variables override any code-level configuration.\n\nExample:\n\n```bash\n# Disable colors\nNO_COLOR=1 node your-app.js\n\n# Enable verbose logging\nDEBUG=1 node your-app.js\n\n# Force colors and verbose logging\nFORCE_COLOR=1 DEBUG=1 node your-app.js\n```\n\n## Examples\n\n### Log to a File\n\nBy default, log files are stored in the typical logging directory for your platform:\n\n```ts\nimport { log, setDefaultLogOptions } from 'lognow'\n\nsetDefaultLogOptions({\n  logJsonToFile: true,\n  name: 'My Application',\n})\n\nlog.info('File this away')\n```\n\n| Platform | Default Log Location                  |\n| -------- | ------------------------------------- |\n| macOS    | `~/Library/Logs/My Application/`      |\n| Linux    | `~/.local/state/My Application/logs/` |\n| Windows  | `%LOCALAPPDATA%\\My Application\\logs\\` |\n\n### Custom Log Directory\n\nSpecify a custom directory for log files:\n\n```ts\nimport { log, setDefaultLogOptions } from 'lognow'\n\nsetDefaultLogOptions({\n  logJsonToFile: '/logs',\n  name: 'My Application',\n})\n\nlog.info('This will be logged to /logs')\n```\n\n### Dual Output: Console and File\n\nLog to both console and file simultaneously:\n\n```ts\nimport { log, setDefaultLogOptions } from 'lognow'\n\nsetDefaultLogOptions({\n  logJsonToFile: true, // Structured logs in file\n  logToConsole: true, // Pretty logs in console\n  name: 'My Application',\n})\n\nlog.info('Logged to both console and file')\n// Console: \"12:47:56.394 INFO [My Application] Logged to both console and file\"\n// File: {\"level\":\"info\",\"messages\":[...], \"name\":\"My Application\",...}\n```\n\n### Electron\n\nLognow automatically manages inter-process communication in Electron applications to merge any logs from the renderer process into your main process' log stream.\n\nIn your main process, e.g. `main.js`, grab the default log instance like you would in any other context — the only difference is that you explicitly import from the `lognow/electron` export instead:\n\n```ts\nimport { log } from 'lognow/electron'\n\nlog.info('Hello from main!')\n\n// The rest of your Electron main process code...\n```\n\nWhen you want to log from the renderer, add the following to your preload script, e.g. `preload.js`. This sets up an inter-process-communication (IPC) channel to ship messages from the renderer to the main process:\n\n```ts\nimport 'lognow/electron/preload'\n\n// The rest of your Electron preload code...\n```\n\nThen, in your renderer / browser code, use the default `lognow/electron` log export as usual:\n\n```ts\nimport { log } from 'lognow/electron'\n\nlog.info('Hello from renderer!')\n\n// The rest of your Electron renderer code...\n```\n\nThat's it. When you run the project, logs from both processes will appear in your main process' console, prefixed with their origin:\n\n```txt\n12:47:56.394 INFO [Main] Hello from main!\n12:47:56.633 INFO [Renderer] Hello from renderer!\n```\n\n\u003e [!WARNING]\n\u003e\n\u003e Timestamps reflect the time of the log entry in the originating process, not the time of the log entry in the receiving process, so timestamps in the main process' console might appear out of order.\n\nElectron support is designed primarily for use with the default log instance — for now, every log instance in the renderer process automatically sends logs to every log instance in the main process, so you can quickly end up with duplicate logs if you have multiple log instances in the main process.\n\n### Libraries\n\nIf you're building a library that others will use, you can use Lognow with dependency injection to allow consumers to integrate their own logging systems.\n\n#### Step 1: Create a logger in your library\n\nIn your library project, create a simple `log.ts` utility file which creates a logger instance used throughout the library:\n\n`the-library/log.ts`:\n\n```ts\nimport type { ILogBasic, ILogLayer } from 'lognow'\nimport { createLogger, injectionHelper } from 'lognow'\n\n/**\n * The default logger instance for the module. Configure log settings here.\n * Exported for use throughout the library.\n */\nexport let log = createLogger({ name: 'YourLibrary' })\n\n/**\n * Set the logger instance for the module. Export this for library consumers to\n * inject their own logger.\n *\n * @param logger - Accepts either a LogLayer instance or a Console- or\n *   Stream-like log target\n */\nexport function setLogger(logger?: ILogBasic | ILogLayer) {\n  log = injectionHelper(logger)\n}\n```\n\nThen use this logger throughout your library:\n\n`the-library/index.ts`:\n\n```ts\nimport { log } from './log.js'\n\n/**\n * A function that uses the library's logger\n */\nexport function greet(name: string) {\n  log.info('Greeting user', name)\n  return `Hello, ${name}!`\n}\n\n// Export the setLogger function so consumers can inject their own logger\nexport { setLogger } from './log.js'\n```\n\n#### Step 2: Inject a logger from the consuming application\n\nIn a different project that uses the library, you can inject a logger instance:\n\n`the-application.ts`:\n\n```ts\nimport { getChildLogger, log } from 'lognow'\nimport { greet, setLogger } from 'the-library'\n\n// The library we've imported has its own lognow instance:\ngreet()\n\n// In our application, we can use the default logger:\nlog.info('Hello from application!')\n\n// We can create and attach a child logger to the default logger,\n// and then inject it into the library to override its internal transports.\nsetLogger(getChildLogger(log, 'child'))\n\n// Now the library logs run through the application's logger,\n// with the chain of inheritance in the context object.\ngreet()\n```\n\nIf the library consumer doesn't want to use `lognow`or `LogLayer`, they can still inject a `Console`- or `WritableStream`-like logger instance into the library to receive basic messages _without_ additional dependencies:\n\n`the-application.ts`:\n\n```ts\nimport { greet, setLogger } from 'the-library'\n\nsetLogger(console)\n\n// Now the library's logs go straight to the passed `console` instance:\ngreet()\n```\n\nOr, since LogLayer provides [many additional transport adapters](https://loglayer.dev/transports), it's easy for library consumers to integrate with their existing logging infrastructure of choice by defining a LogLayer instance to their liking:\n\n`the-application.ts`:\n\n```ts\nimport { PinoTransport } from '@loglayer/transport-pino'\nimport { LogLayer } from 'loglayer'\nimport { pino } from 'pino'\nimport { greet, setLogger } from 'the-library'\n\nconst pinoLogger = pino({\n  level: 'trace',\n})\n\nconst log = new LogLayer({\n  transport: new PinoTransport({\n    logger: pinoLogger,\n  }),\n})\n\nsetLogger(log)\n\n// Now the library's logs are passed to the pino logger:\ngreet()\n// Logs: \"Hello from library!\"\n```\n\n## Background\n\n### Implementation notes\n\n#### Why free functions\n\nWhy do we have to use free functions to manipulate the default log instance instead of calling `log.options()` or something?\n\nLognow exposes a standard LogLayer instance so you can trivially ditch lognow without modifying any of your logging call sites.\n\nThis brings a slight compromise in discoverability: Additional configuration management and convenience functions must be provided via procedural-style free functions instead of extensions of the `LogLayer` class itself.\n\n#### Pretty printing objects\n\nIn the browser, you can pass objects directly to the console, but logging to a stream requires object serialization, formatting, and colorization.\n\nMany strategies were evaluated for pretty printing: [node:util inspect](https://nodejs.org/api/util.html#utilinspectvalue-options), [node-inspect-extracted](https://github.com/hildjj/node-inspect-extracted), [json-stringify-pretty-compact](https://github.com/lydell/json-stringify-pretty-compact), [pretty-format](https://github.com/facebook/jest/tree/main/packages/pretty-format), [stringify-object](https://github.com/sindresorhus/stringify-object), [loupe](https://github.com/sindresorhus/loupe), [object-inspect](https://github.com/inspect-js/object-inspect), and [tslog](https://github.com/fullstack-build/tslog).\n\nI found that it's hard to improve on node's native inspect implementation for handling unusual object types. A universal port of `inspect` is used for serialization to stream targets in the browser environment.\n\n#### Serializing complex objects for file logs\n\nMany packages exist for serializing complex JavaScript objects into valid JSON, but few are equipped to handle esoteric data types and fewer still emphasize human readability of the output over being evaluable as JavaScript.\n\nHuman readability seems more important than perfect round-trip evaluation for the kind of context and metadata objects we're likely to want to log. (Of course the logs remain parsable, but complex objects like functions and circular references are rendered as strings.)\n\nI found that [safe-stable-stringify](https://github.com/BridgeAR/safe-stable-stringify) does a nice job of this in combination with the [serialize-error](https://github.com/sindresorhus/serialize-error) package for Error object serialization.\n\n#### Serializing complex objects for Electron IPC\n\nLog objects must be serialized for transport between processes in Electron. Here, we _do_ care about parsability, since the log object must be deserialized before it's passed into the main process' log instance. [SuperJson](https://github.com/flightcontrolhq/superjson), [devalue](https://github.com/Rich-Harris/devalue), and [next-json](https://github.com/iccicci/next-json) were evaluated.\n\nOnly next-json successfully round-tripped the [nightmare object](https://github.com/kitschpatrol/lognow/blob/main/test/assets/nightmare-object.ts) used in testing.\n\n## Maintainers\n\n[kitschpatrol](https://github.com/kitschpatrol)\n\n## Acknowledgments\n\nThanks to [Theo Gravity](https://suteki.nu) for developing [LogLayer](https://loglayer.dev), and for responding to my questions so quickly and helpfully.\n\n\u003c!-- contributing --\u003e\n\n## Contributing\n\n[Issues](https://github.com/kitschpatrol/lognow/issues) are welcome and appreciated.\n\nPlease open an issue to discuss changes before submitting a pull request. Unsolicited PRs (especially AI-generated ones) are unlikely to be merged.\n\nThis repository uses [@kitschpatrol/shared-config](https://github.com/kitschpatrol/shared-config) (via its `ksc` CLI) for linting and formatting, plus [MDAT](https://github.com/kitschpatrol/mdat) for readme placeholder expansion.\n\n\u003c!-- /contributing --\u003e\n\n\u003c!-- license --\u003e\n\n## License\n\n[MIT](license.txt) © [Eric Mika](https://ericmika.com)\n\n\u003c!-- /license --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkitschpatrol%2Flognow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkitschpatrol%2Flognow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkitschpatrol%2Flognow/lists"}