PHPackages                             koriym/semantic-logger - 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. koriym/semantic-logger

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

koriym/semantic-logger
======================

Type-safe structured logging with schema validation and meaningful context

0.8.0(2mo ago)06.0k↑229.2%[1 issues](https://github.com/koriym/Koriym.SemanticLogger/issues)3MITPHPPHP ^8.1CI passing

Since Aug 3Pushed 4w agoCompare

[ Source](https://github.com/koriym/Koriym.SemanticLogger)[ Packagist](https://packagist.org/packages/koriym/semantic-logger)[ RSS](/packages/koriym-semantic-logger/feed)WikiDiscussions 1.x Synced today

READMEChangelog (10)Dependencies (6)Versions (33)Used By (3)

Koriym.SemanticLogger
=====================

[](#koriymsemanticlogger)

Type-safe semantic logging for hierarchical application workflows with tree-shaped JSON output.

`Koriym.SemanticLogger` records three kinds of facts as structured JSON:

- `open`: what is starting
- `event`: what happened while it was running
- `close`: how it ended

Each entry carries a schema URL and typed context data, so logs stay machine-readable and can be validated, rendered, and inspected without depending on free-form log messages.

The public log contract is structural: matched `close` entries and scoped `events` are nested under the `open` entry they belong to. Once serialized to JSON, normal operation results are read from paths such as `open[0].close` — not from a separate top-level close list.

Installation
------------

[](#installation)

```
composer require koriym/semantic-logger
```

Core Model
----------

[](#core-model)

Semantic logs are built from matching `open` / `close` pairs plus optional `event`s:

```
request open
  database_query event
  cache_lookup event
request close

```

This gives you:

- explicit operation boundaries
- nested workflow structure
- intent vs outcome
- schema-backed context instead of ad-hoc strings
- parent-child relationships embedded in the tree — no need to correlate open/close pairs by ID

Optional RFC 8288 links can be attached at flush time when you want to point to related resources such as source code, schemas, or external specs.

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

[](#quick-start)

### 1. Define Context Classes

[](#1-define-context-classes)

```
use Koriym\SemanticLogger\AbstractContext;

final class ProcessContext extends AbstractContext
{
    public const TYPE = 'process';
    public const SCHEMA_URL = 'https://example.com/schemas/process.json';

    public function __construct(
        public readonly string $name,
    ) {}
}

final class ProcessEventContext extends AbstractContext
{
    public const TYPE = 'process_event';
    public const SCHEMA_URL = 'https://example.com/schemas/process-event.json';

    public function __construct(
        public readonly string $message,
    ) {}
}

final class ProcessResultContext extends AbstractContext
{
    public const TYPE = 'process_result';
    public const SCHEMA_URL = 'https://example.com/schemas/process-result.json';

    public function __construct(
        public readonly string $status,
    ) {}
}
```

### 2. Log a Workflow

[](#2-log-a-workflow)

```
use Koriym\SemanticLogger\SemanticLogger;

$logger = new SemanticLogger();

$processId = $logger->open(new ProcessContext('data import'));
$logger->event(new ProcessEventContext('processing started'));
$logger->close(new ProcessResultContext('success'), $processId);

$links = [
    [
        'rel' => 'describedby',
        'href' => 'https://example.com/specs/import-flow',
        'title' => 'Import Flow Specification',
    ],
];

$log = $logger->flush($links);

echo json_encode($log, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
```

### 3. Output Shape

[](#3-output-shape)

`flush()` returns a `LogJson` object. Its public JSON shape is tree-oriented: each `open` node contains nested child `open`s, scoped `events`, and its matching `close` object. Top-level `events` and `close` are reserved for root-scope or orphan diagnostics when structural placement is not possible, so normal consumers should treat the tree as the source of truth.

```
{
  "$schema": "https://koriym.github.io/Koriym.SemanticLogger/schemas/semantic-log.json",
  "open": [
    {
      "id": "process_1",
      "type": "process",
      "schemaUrl": "https://example.com/schemas/process.json",
      "context": {
        "name": "data import"
      },
      "events": [
        {
          "id": "process_event_1",
          "type": "process_event",
          "schemaUrl": "https://example.com/schemas/process-event.json",
          "context": {
            "message": "processing started"
          }
        }
      ],
      "close": {
        "id": "process_result_1",
        "type": "process_result",
        "schemaUrl": "https://example.com/schemas/process-result.json",
        "context": {
          "status": "success"
        }
      }
    }
  ],
  "links": [
    {
      "rel": "describedby",
      "href": "https://example.com/specs/import-flow",
      "title": "Import Flow Specification"
    }
  ]
}
```

### Open / Close Ordering

[](#open--close-ordering)

`open` and `close` must be paired in LIFO order. Violations raise exceptions from `Koriym\SemanticLogger\Exception`:

- `InvalidOperationOrderException` — `close()` called with an id other than the innermost open
- `NoOpenOperationsException` — `close()` called with no open operation on the stack
- `UnclosedLogicException` — `flush()` called while opens are still pending

Wrap work in `try/finally` so `close()` always runs, even on error paths.

### Try It

[](#try-it)

A runnable end-to-end example lives under `demo/` (see `demo/run.php`, `demo/e-commerce.php`). `composer demo` runs it with XHProf + Xdebug enabled, validates the output, and renders it with `stree`.

Development Logs
----------------

[](#development-logs)

Use `DevLogger` to write logs to disk during development:

```
use Koriym\SemanticLogger\DevLogger;
use Koriym\SemanticLogger\SemanticLogger;

$semanticLogger = new SemanticLogger();
$devLogger = new DevLogger(__DIR__ . '/var/log');

$operationId = $semanticLogger->open(new ProcessContext('data import'));
$semanticLogger->event(new ProcessEventContext('processing started'));
$semanticLogger->close(new ProcessResultContext('success'), $operationId);

$devLogger->log($semanticLogger);
```

Each call writes a file named `semantic-dev---.json` (microsecond timestamp, PID, and `uniqid()` suffix) to the given directory, or to `sys_get_temp_dir()` when no directory is passed.

`DevLogger` writes the same public tree JSON shape returned by `flush()`, which works naturally with `stree`. Top-level `events` and `close` are reserved for root-scope or orphan diagnostics when structural placement is not possible.

Profiling (XHProf / Xdebug)
---------------------------

[](#profiling-xhprof--xdebug)

Wrap a `SemanticLogger` in `DevSemanticLogger` to attach per-operation profiler artifacts to each matched `close` entry:

```
use Koriym\SemanticLogger\DevSemanticLogger;
use Koriym\SemanticLogger\SemanticLogger;

$logger = new DevSemanticLogger(new SemanticLogger());
// open() / event() / close() / flush() as usual
```

When XHProf and/or Xdebug are enabled, each `close` in the tree gains a `profile` object with `wallTime` plus `xhprofProfile` / `xdebugTrace` segments captured while that operation was active:

```
"close": {
  "id": "process_result_1",
  "type": "process_result",
  "context": { "status": "success" },
  "profile": {
    "wallTime": 0.0123,
    "xhprofProfile": [{ "path": "/tmp/xhprof-...xhprof" }],
    "xdebugTrace":   [{ "path": "/tmp/trace-...xt" }]
  }
}
```

Segments are attributed only to the operation they ran inside — the parent's profile pauses while a child is open. No extensions required to use the logger itself; `DevSemanticLogger` simply skips profile attachment when XHProf / Xdebug are not loaded.

Validate Semantic Logs
----------------------

[](#validate-semantic-logs)

```
vendor/bin/validate-semantic-log.php path/to/semantic-log.json [path/to/schemas]
```

The envelope (root tree) is always validated against the bundled `docs/schemas/semantic-log.json` shipped in the composer dist. You supply a schema directory for your own context schemas — the CLI defaults to `./schemas/` when the second argument is omitted.

```
use Koriym\SemanticLogger\SemanticLogValidator;

$validator = new SemanticLogValidator();
$validator->validate('path/to/semantic-log.json', 'path/to/context-schemas');
```

Validation checks:

- nested log structure
- schema URL resolution
- context payloads against their schemas
- detailed validation errors when something does not match

Semantic Tree Visualizer
------------------------

[](#semantic-tree-visualizer)

`vendor/bin/stree` renders semantic logs as a readable tree:

```
vendor/bin/stree debug.json
vendor/bin/stree --full debug.json
vendor/bin/stree --threshold=10ms slow.json
vendor/bin/stree --json debug.json
```

### Options

[](#options)

OptionShortDescription`--threshold=T``-t`Time threshold filter such as `10ms` or `0.5s``--lines=N``-l`Maximum lines for multi-line data (`0` means no limit)`--full``-f`Show the full tree`--values``-V`Let registered formatters show values instead of only keys`--json`Pretty-print raw JSON`--help``-h`Display help### Rendering in process

[](#rendering-in-process)

`stree` is a thin CLI over the same renderer you can call directly. `LogJson::render()` takes a `LogRendererInterface` and hands the log to it (double dispatch), so the log never needs to know the output format — you swap the renderer instead:

```
use Koriym\SemanticLogger\Stree\TreeRenderer;
use Koriym\SemanticLogger\Stree\RenderConfig;

$log = $logger->flush();

echo $log->render(new TreeRenderer());                              // compact tree
echo $log->render(new TreeRenderer(new RenderConfig(true, 0.0, 5))); // full tree
```

This keeps the tree compact for both humans (visual parent/child structure) and AI consumers (far fewer tokens than the raw JSON). Implement `LogRendererInterface` to add your own output format (e.g. Markdown or Mermaid) without changing the logger.

Use Cases
---------

[](#use-cases)

- trace nested application workflows
- compare planned work vs actual result
- keep audit-friendly structured records
- inspect development logs without parsing free-form text
- render semantic logs as trees during debugging

Documentation
-------------

[](#documentation)

- [Schema Portal](https://koriym.github.io/Koriym.SemanticLogger/)
- [CHANGELOG.md](CHANGELOG.md)

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance91

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 97.5% 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.

###  Release Activity

Cadence

Every ~30 days

Recently: every ~11 days

Total

11

Last Release

29d ago

Major Versions

0.8.0 → 1.x-dev2026-06-05

### Community

Maintainers

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

---

Top Contributors

[![koriym](https://avatars.githubusercontent.com/u/529021?v=4)](https://github.com/koriym "koriym (194 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (5 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/koriym-semantic-logger/health.svg)

```
[![Health](https://phpackages.com/badges/koriym-semantic-logger/health.svg)](https://phpackages.com/packages/koriym-semantic-logger)
```

###  Alternatives

[composer/composer

Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.

29.5k196.2M3.1k](/packages/composer-composer)[infection/infection

Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.

2.2k28.9M2.4k](/packages/infection-infection)[paycore/openfintech-data

Openfintech data

22110.1k](/packages/paycore-openfintech-data)[pantheon-systems/terminus

A command line interface for Pantheon

3391.5M18](/packages/pantheon-systems-terminus)[wikimedia/parsoid

Parsoid, a bidirectional parser between wikitext and HTML5

187557.3k3](/packages/wikimedia-parsoid)[drupal/core-dev

require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.

2022.6M343](/packages/drupal-core-dev)

PHPackages © 2026

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