mrkeyoor.com_
Thu 06 Aug 07:42 UTC
npmWeb Frontendupdated 06 Aug 2026

loglevel

loglevel is a very small wrapper around the console object that adds log levels and nothing else. You get log.trace, log.debug, log.info, log.warn, and log.error, plus setLevel to hide everything below a threshold and silent to turn it all off. Its defining trick is that enabled methods are bound directly to the underlying console methods rather than called through a wrapper, so the browser devtools still show the file and line number of your own code instead of a line inside the logging library. It also degrades gracefully: if console.debug does not exist it falls back to console.log, and if there is no console at all the calls become no-ops instead of throwing. Levels can persist to localStorage, so a developer can type log.setLevel('trace') in a production console and keep verbose logging across reloads. Named child loggers via getLogger let each module carry its own level.

Verdict

For browser code that just needs level filtering without losing console line numbers, loglevel is still the right tool and almost nothing competes at 1.4 KB. On the server, or the moment you need formatting, context, or log shipping, move to pino or consola instead of stacking plugins onto a library that was designed not to do those things.

API stability5/5The API has been the same five methods plus setLevel since the 1.x line began, and recent additions (resetLevel, rebuild, Symbol logger names) are additive. Upgrading across minor versions has been uneventful for years.
Docs5/5The README documents every method with its edge cases, explains why plugins cost you stack traces, shows the CommonJS, AMD, script tag, and ESM loading variants, and includes a live browser demo. The honesty about what the library refuses to do is unusual.
Maintenance3/5One maintainer, 1.9.2 released September 2024, last push March 2025, 17 open issues (19 counting PRs). The code is small and stable enough that low activity is not alarming, but there is no second maintainer and no recent release.
Ecosystem3/5Roughly 21M weekly downloads, largely as a transitive dependency of browser SDKs and older frontend tooling. The plugin list in the README is short and the notable plugins have not been touched in years.

Use it if

  • You are shipping browser code and want the console line numbers preserved, which almost every other logging library destroys by routing calls through a wrapper function
  • You want production logs quiet by default but debuggable on demand: setLevel persists to localStorage so a developer or support engineer can turn on trace logging from the devtools console and have it survive reloads
  • You are writing a library and want per-module loggers your consumers can turn up or down individually, without forcing a logging framework on them
  • Bundle size is the constraint: it is a single file with zero dependencies that the README puts at 1.4 KB minified and gzipped
Skip it if

Setup reality

npm install loglevel is as easy as it gets: no dependencies, no build step, TypeScript definitions included in the package. Three defaults reliably confuse people on the first day. The default level is warn, so log.info and log.debug print nothing at all and the library looks broken until you call enableAll or setLevel. setLevel persists to localStorage (falling back to cookies) unless you pass false as the second argument, which means a level someone set months ago in their browser silently overrides what your code asks for; setDefaultLevel is the call you actually want at startup because it only applies when nothing was persisted. Child loggers created with getLogger snapshot the root level at creation time, so changing the root level later does nothing to them until you call log.rebuild(). In ESM, the UMD shape means `import log from 'loglevel'` works in most bundlers while others need `import * as log`. There is no ambient global unless you load the dist file with a script tag, in which case it claims window.log and you may need noConflict.

Patterns

Log at each levelbasic-logging

import log from 'loglevel'

log.trace('entering handler')
log.debug('payload', payload)
log.info('user signed in')
log.warn('retrying request')
log.error('request failed', err)

Only warn and error print out of the box because the default level is warn. log.log exists as an alias for log.debug so you can search and replace console.log without thinking.

Change the active levelset-level

import log from 'loglevel'

log.setLevel('debug')        // string, case-insensitive
log.setLevel(log.levels.WARN) // enum, type-safe
log.setLevel(0)               // 0 trace through 5 silent

setLevel persists the choice to localStorage, falling back to cookies. That is a feature in a browser console and a trap in application code, because it outlives the page load.

Set a level without overriding the developerdefault-level-at-startup

import log from 'loglevel'

log.setDefaultLevel(process.env.NODE_ENV === 'production' ? 'error' : 'debug')

This is the call for application startup. It only applies when no level was persisted, so someone who ran setLevel('trace') in the console keeps their setting across reloads instead of having it stamped back to error.

Change level without writing to storagedisable-persistence

import log from 'loglevel'

log.setLevel('info', false) // second arg false skips localStorage
log.resetLevel()            // back to the default and clears the stored value

Pass false when the level comes from a config value or a feature flag; otherwise the first render permanently pins that user's log level. resetLevel is the way to clear a stale persisted level.

Give each module its own loggernamed-loggers

// checkout.js
import { getLogger } from 'loglevel'
const log = getLogger('checkout')
log.debug('cart recalculated')

// anywhere, including the devtools console
import rootLog from 'loglevel'
rootLog.getLogger('checkout').setLevel('trace')

getLogger with the same name always returns the same instance, so calling it in every file is fine. Names must be non-empty strings or Symbols; Symbol-named loggers never persist their level.

Propagate a root level change to existing loggersrebuild-child-loggers

import log from 'loglevel'

const child = log.getLogger('billing') // inherits WARN at creation

log.setLevel('error')
child.getLevel() // still WARN

log.rebuild()
child.getLevel() // now ERROR

Child loggers copy the root level only when they are first created. This is the single most reported surprise in the library, and rebuild() is the fix. Children that called setLevel themselves keep their own level.

Flip all logging on or offenable-or-silence-everything

import log from 'loglevel'

log.enableAll()  // same as setLevel('trace')
log.disableAll() // same as setLevel('silent')

Both persist like setLevel does. Handy to type into a browser console during an incident, less handy to leave in shipped code.

Skip work that only exists for a log lineguard-expensive-logging

import log from 'loglevel'

if (log.getLevel() <= log.levels.DEBUG) {
  log.debug(buildExpensiveDiagnostic())
}

Only worth it when profiling proved the payload construction is expensive. For plain string building, pass multiple arguments (log.debug('a', b, c)) instead; disabled methods are no-ops so nothing gets concatenated.

Add a prefix through methodFactoryprefix-plugin

import log from 'loglevel'

const originalFactory = log.methodFactory
log.methodFactory = function (methodName, logLevel, loggerName) {
  const rawMethod = originalFactory(methodName, logLevel, loggerName)
  return function (...args) {
    rawMethod(`[${String(loggerName ?? 'root')}]`, ...args)
  }
}
log.rebuild()

You must call rebuild() or the new factory is never used. Wrapping in a function means the console now reports this file as the log site, losing the line-number advantage; that cost is the reason the plugin API is so minimal.

Forward errors to a collectorship-logs-to-a-server

import log from 'loglevel'

const originalFactory = log.methodFactory
log.methodFactory = function (methodName, logLevel, loggerName) {
  const rawMethod = originalFactory(methodName, logLevel, loggerName)
  return function (...args) {
    rawMethod(...args)
    if (methodName === 'error') {
      navigator.sendBeacon('/logs', JSON.stringify({ logger: String(loggerName), args: args.map(String) }))
    }
  }
}
log.rebuild()

There is no built-in transport, so this hand-rolled version or the loglevel-plugin-remote package is the whole story. Do not call fetch here without a guard: an error log inside your fetch error handler will loop.

Import it in ESM without interop errorsesm-import-interop

// works in most bundlers and Node ESM
import log from 'loglevel'

// some toolchains need the namespace form instead
import * as log from 'loglevel'

The package is a single UMD file, and the README documents both forms because loaders disagree about what the default export is. If TypeScript complains about calling methods on a namespace import, switch to the default form.

Use the script tag build without stealing window.logavoid-global-conflict

<script src="https://unpkg.com/loglevel/dist/loglevel.min.js"></script>
<script>
  var logging = log.noConflict();
  logging.warn('now safe alongside other libraries');
</script>

noConflict restores whatever window.log was before and hands you the logger. It only exists on the root logger, not on instances from getLogger.

Alternatives

PackageRegistryPick it when
pinonpmYou are logging server-side and need structured JSON, child bindings, redaction, and fast async transports
debugnpmYou want namespaced developer logging toggled by a DEBUG environment variable or localStorage key rather than numeric levels
consolanpmYou want pretty formatted output with tags, boxes, and reporters that works in both Node and the browser
loglevel-plugin-prefixnpmYou are staying on loglevel but need timestamps and logger names prefixed onto each message