PHPackages                             sugarcraft/candy-log - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Logging &amp; Monitoring](/categories/logging)
4. /
5. sugarcraft/candy-log

ActiveLibrary[Logging &amp; Monitoring](/categories/logging)

sugarcraft/candy-log
====================

PHP port of charmbracelet/log — minimal, colorful leveled logging with structured human-readable output, text/JSON/logfmt formatters, and stdlog adapter.

001PHP

Since Jun 29Pushed 1mo agoCompare

[ Source](https://github.com/sugarcraft/candy-log)[ Packagist](https://packagist.org/packages/sugarcraft/candy-log)[ RSS](/packages/sugarcraft-candy-log/feed)WikiDiscussions master Synced 3w ago

READMEChangelogDependenciesVersions (1)Used By (1)

[![candy-log](.assets/icon.png)](.assets/icon.png)

[![CI](https://github.com/detain/sugarcraft/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/detain/sugarcraft/actions/workflows/ci.yml)[![codecov](https://camo.githubusercontent.com/1d12f7de4de347cf8f9c51ad1679f60168dcfbdf273ec0ba3e644d2abbb4dd97/68747470733a2f2f636f6465636f762e696f2f67682f64657461696e2f737567617263726166742f6272616e63682f6d61737465722f67726170682f62616467652e7376673f666c61673d63616e64792d6c6f67)](https://app.codecov.io/gh/detain/sugarcraft?flags%5B0%5D=candy-log)[![Packagist Version](https://camo.githubusercontent.com/ae3f1cf4a42acf34fea5923a1b0b76ad46a7400015ef7530447627eae38e1405/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f737567617263726166742f63616e64792d6c6f673f6c6162656c3d7061636b6167697374)](https://packagist.org/packages/sugarcraft/candy-log)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![PHP](https://camo.githubusercontent.com/e78ffc83837c0d12647811a7fd1910c3cbeae04988de94bb4fd5b67e0874696a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d254532253839254135382e312d3838393262662e737667)](https://www.php.net/)

CandyLog
========

[](#candylog)

PHP port of [charmbracelet/log](https://github.com/charmbracelet/log) — a minimal, colorful leveled logging library.

Features
--------

[](#features)

- **Leveled logging** — `Debug`, `Info`, `Warn`, `Error`, `Fatal` levels
- **Colorful human-readable output** — terminal-styled by default (Probe-driven: respects `NO_COLOR` / `FORCE_COLOR`)
- **Multiple formatters** — `TextFormatter` (default), `JSONFormatter`, `LogfmtFormatter`
- **Structured key/value pairs** — pass arbitrary context with every log call
- **Sub-loggers** — `with([...])` creates a child logger with persistent fields
- **Per-field styling** — `Styles::keys` maps field names to their ANSI styles
- **syslog-aligned levels** — integer values (-4/0/4/8/12) for easy threshold filtering
- **stdlog adapter** — wrap in `Log\StandardLogAdapter` for `*log.Logger` interface compatibility
- **PSR-3 bridge** — `PsrBridge` wrapper provides full PSR-3 `LoggerInterface` methods
- **Hook system** — register callbacks per log level via `HookRegistry::onLevel()`
- **Configurable log-part ordering** — `PartsOrder` DTO controls which parts appear and in what sequence

Install
-------

[](#install)

```
composer require sugarcraft/candy-log
```

Quick Start
-----------

[](#quick-start)

```
use SugarCraft\Log\Logger;
use SugarCraft\Log\Level;

$log = Logger::new();
$log->info('Starting oven', ['degree' => 375]);
$log->warn('Almost ready', ['batch' => 2]);
$log->error('Temperature too low', ['err' => 'underheated']);
```

Levels
------

[](#levels)

Levels are syslog-aligned integers — use `->value` for threshold comparisons:

```
Level::Debug->value; // -4
Level::Info->value;  //  0
Level::Warn->value;  //  4
Level::Error->value;  //  8
Level::Fatal->value;  // 12

$log->info('info message');
$log->warn('warn message');
$log->error('error message');
$log->fatal('fatal message'); // throws RuntimeException
$log->print('always prints');   // no level prefix
```

Structured Fields
-----------------

[](#structured-fields)

```
$log->info('Baking cookies', [
    'flour' => '2 cups',
    'butter' => true,
    'temp' => 375,
]);

// Child logger with persistent fields
$baker = $log->with(['user' => 'chef', 'session' => 'am']);
$baker->info('Batch started'); // also has user + session
```

Formatters
----------

[](#formatters)

```
use SugarCraft\Log\Formatter\TextFormatter;
use SugarCraft\Log\Formatter\JsonFormatter;
use SugarCraft\Log\Formatter\LogfmtFormatter;

$log = Logger::new(formatter: new JsonFormatter());
```

Styling
-------

[](#styling)

Styles are applied automatically when the terminal supports color output. Color is determined by `candy-palette`'s Probe — it respects the `NO_COLOR`and `FORCE_COLOR` environment variables.

Override level styles via `Logger::styles()`:

```
use SugarCraft\Sprinkles\Style;
$log = Logger::new();
$styles = $log->styles();
$styles->levels[Level::Error] = Style::new()->foreground('red')->bold();
$log->setStyles($styles);
```

### Per-field styles

[](#per-field-styles)

`Styles::keys` maps field names (`time`, `level`, `prefix`, `caller`, `message`, `key`, `value`) to individual `Style` objects:

```
$styles = $log->styles();
$styles->keys['time']   = Style::new()->foreground('cyan');
$styles->keys['caller'] = Style::new()->foreground('grey');
$log->setStyles($styles);
```

### Level text alignment

[](#level-text-alignment)

`Styles::padLevelText($label)` right-pads a level label to 5 characters for column-aligned log output:

```
Styles::padLevelText('INFO');  // "INFO "
Styles::padLevelText('DEBUG'); // "DEBUG"
```

Panic Handlers
--------------

[](#panic-handlers)

```
use SugarCraft\Log\Log;

// Install a panic handler that catches uncaught exceptions and fatal errors,
// restores the terminal from altscreen mode, and prints a styled panic report.
Log::installPanicHandler();

// Restore terminal state manually (exit altscreen, show cursor).
// Called automatically by the panic handler, but safe to call directly.
Log::restoreTerminal();
```

The panic handler catches uncaught exceptions and fatal errors (E\_ERROR, E\_PARSE), restores the terminal to a usable state, and prints a colorized banner with the exception class, message, and backtrace.

PSR-3 Bridge
------------

[](#psr-3-bridge)

`PsrBridge` wraps a `Logger` instance and provides the full PSR-3 `LoggerInterface` API (`emergency`, `alert`, `critical`, `error`, `warning`, `notice`, `info`, `debug`, `log`). Use it anywhere a PSR-3 logger is expected:

```
use SugarCraft\Log\Logger;
use SugarCraft\Log\PsrBridge;
use Psr\Log\LogLevel;

$logger = new Logger();
$psr = new PsrBridge($logger);

// All PSR-3 methods available
$psr->warning('Something is off', ['detail' => 'temperature rising']);
$psr->log(LogLevel::ERROR, 'Operation failed', ['code' => 500]);
```

The bridge also fires registered hooks before forwarding each message to the underlying logger, enabling middleware-style interceptors.

Hook System
-----------

[](#hook-system)

The hook system lets you register callbacks that fire whenever a log entry is emitted at or above a given level. Hooks receive the `Level`, PSR-3 level string, message, and context — useful for dispatching to external services, enriching context, or filtering.

```
use SugarCraft\Log\Logger;
use SugarCraft\Log\Level;
use SugarCraft\Log\Hook\HookRegistry;

$logger = new Logger();
$hooks = new HookRegistry();

// Register a callback for all Warn-and-above entries
$id = $hooks->onLevel(Level::Warn, function (Level $level, string $psrLevel, string $message, array $context) {
    // Dispatch to external service, enrich context, etc.
    file_put_contents('/tmp/warn.log', "[{$level->label()}] {$message}\n", FILE_APPEND);
});

// Pass hooks to the PsrBridge, or fire them manually
$hooks->fire(Level::Warn, 'warning', 'Something is off', []);
```

`HookRegistry::onLevel(Level, callable)` returns a registration ID. `HookRegistry::fire(Level, psrLevel, message, context)` dispatches to all handlers whose minimum level is met.

The `Hook` interface is also available for structured implementations:

```
use SugarCraft\Log\Hook\Hook;
use SugarCraft\Log\Level;

final class MetricsHook implements Hook
{
    public function onLevel(Level $level, string $psrLevel, string $message, array $context): void
    {
        // Ship metrics to your observability platform
    }
}
```

Parts Order
-----------

[](#parts-order)

`PartsOrder` is a config DTO that controls which log-parts appear and in what sequence when formatting. It ships with three named presets:

```
use SugarCraft\Log\PartsOrder;

// Default: timestamp level prefix? caller? message fields?
PartsOrder::default();   // [timestamp, level, prefix, caller, message, fields]

// Syslog-friendly: omits prefix and caller
PartsOrder::syslog();     // [timestamp, level, message, fields]

// Message-first: message comes before level and timestamp
PartsOrder::messageFirst(); // [message, level, timestamp, fields]

// Custom ordering
$order = new PartsOrder([PartsOrder::PART_MESSAGE, PartsOrder::PART_LEVEL, PartsOrder::PART_FIELDS]);

// Query whether a part is included
$order->has(PartsOrder::PART_CALLER); // false for syslog(), true for default()
```

Named part constants: `PART_TIMESTAMP`, `PART_LEVEL`, `PART_PREFIX`, `PART_CALLER`, `PART_MESSAGE`, `PART_FIELDS`.

Caller Information
------------------

[](#caller-information)

`CallerFormatter::find()` walks the call stack and returns `"file:line"` of the first frame outside the log package — the true call site:

```
use SugarCraft\Log\CallerFormatter;

$caller = CallerFormatter::find(); // e.g. "my-script.php:42"
```

Used internally by formatters when `$reportCaller` is enabled on the `Logger`.

License
-------

[](#license)

[MIT](LICENSE)

###  Health Score

19

—

LowBetter than 9% of packages

Maintenance59

Moderate activity, may be stable

Popularity0

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/b1036e0717211b8030b83cbe729e8ba6ba442fdbd5285fb97a39d7dcfe339342?d=identicon)[detain](/maintainers/detain)

---

Top Contributors

[![detain](https://avatars.githubusercontent.com/u/1364504?v=4)](https://github.com/detain "detain (78 commits)")

### Embed Badge

![Health badge](/badges/sugarcraft-candy-log/health.svg)

```
[![Health](https://phpackages.com/badges/sugarcraft-candy-log/health.svg)](https://phpackages.com/packages/sugarcraft-candy-log)
```

###  Alternatives

[psr/log

Common interface for logging libraries

10.4k1.2B11.9k](/packages/psr-log)[open-telemetry/api

API for OpenTelemetry PHP.

2041.5M289](/packages/open-telemetry-api)[open-telemetry/sdk

SDK for OpenTelemetry PHP.

2428.5M356](/packages/open-telemetry-sdk)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
