← Basics
Publishedrole: foundationlicense: BSD-3-Clause

tom_basics

tom_basics · v1.0.0

The bottom of the Tom dependency stack: a UUID-tracked exception base with folded stack traces, a per-class bitwise logger, a platform-abstraction seam, and a runtime model — on just three well-known dependencies.

View repository → See License
Status
Published
LOC
595
Tests
6
Test LOC
50

Overview

From the module readme.md file:

What it enables

Enables Tracked framework exceptions, tom_core_kernel foundation, Shared base utilities.

Relationships

Standalone — no declared relationships.

tom_basics

> Part of the Tom framework by al-the-bear. > © 2024–2026 Peter Nicolai Alexis Kyaw — BSD-3-Clause, see LICENSE.

Basic utilities for the Tom framework including exception handling with UUID tracking.

tom_basics is the bottom of the dependency stack. It carries the few primitives that almost every other Tom package imports — a traceable exception base, a configurable logger, a platform-abstraction layer, and a small runtime environment model — while keeping its own dependency list to just three well-known packages (uuid, stack_trace, http). Nothing here pulls in Flutter, a database, or a server framework, so it is safe to import from the most foundational corners of the codebase.

---

Overview

A foundational library has one job: give the layers above it a vocabulary they can share without dragging in heavy dependencies. tom_basics provides four such vocabularies, each independent of the others:

  • Errors that can be traced across process boundaries. A

TomBaseException stamps every failure with a UUID at the moment it is created. When the same error is logged on a server, surfaced in a client, and filed in a bug tracker, the UUID is the thread that ties the three sightings together. The exception also captures a terse, core-folded stack trace (via stack_trace) so the noise of the SDK internals is stripped from the report.

  • Logging you can dial up and down per class. The global

tomLog instance offers nine severity methods (tracefatal plus status) and a bitwise level model that lets you compose, subtract, and match levels. You can raise the verbosity of a single class or method without touching the global level, and you can redirect all output to a destination of your choosing by swapping one field.

  • Platform detail behind a single seam. Code that needs to know "are we on

the web?" or "give me an HTTP client" talks to TomPlatformUtils.current instead of importing dart:io or dart:html. The concrete implementation is injected once at startup, so the same library code compiles and runs on console, server, mobile, and web.

  • A runtime environment model for wiring. [TomEnvironment,

TomPlatform, and TomRuntime](#runtime-environments-and-platforms) describe which environment (development / test / production) and which platform (web / macos / android / …) are active, with a parent hierarchy. Higher-level Tom packages use this to select environment- and platform-specific implementations.

These four concerns share no code with each other; you can import the package and use only the logger, only the exception base, or only the platform seam.

---

Installation

Add the dependency with its hosted version constraint:

dependencies:
  tom_basics: ^1.0.0

or from the command line:

dart pub add tom_basics

SDK: Dart ^3.10.0. Transitive dependencies: uuid, stack_trace, http — all pure-Dart and platform-neutral, so tom_basics itself adds no native or Flutter requirement.

---

Features

Exception handling

CapabilityType / memberNotes
UUID-stamped exception TomBaseException Auto-generates a UUIDv4 unless one is supplied.
Request correlation requestUuid Optional id tying an error to an inbound request.
Structured context parameters Map<String, Object?> of diagnostic values.
Cause chaining rootException The underlying error this one wraps.
Terse stack trace stackTrace, printStackTrace() Core frames folded out via stack_trace.
Creation timestamp timeStamp UTC DateTime set at construction.

Logging

CapabilityType / memberNotes
Global logger tomLog Ready-to-use TomLogger singleton.
Severity methods trace debug traffic info warn status error severe fatal One method per level.
Bitwise levels TomLogLevel + - matches Compose and subtract levels.
Compound levels development extended production still silent off Named presets.
Per-name overrides addNameLevel / setLogLevelExceptions Raise verbosity for one class or method.
Level stack pushLogLevel / popLogLevel Temporary, scoped verbosity.
Pluggable output logOutput, TomLogOutput Swap in file / remote / custom sinks.
Custom rendering TomLoggable Control how your objects appear in logs.

Platform abstraction

CapabilityType / memberNotes
Injectable singleton TomPlatformUtils.current / setCurrentPlatform One seam for all platform detail.
Environment type isDesktop isMobile isWeb Coarse-grained checks.
OS detection isWindows isLinux isMacOs isFuchsia isAndroid isIos Fine-grained checks.
Console output out / outError Platform-routed stdout / stderr.
HTTP client factory httpClient() Returns a package:http Client.
Env vars envVars / getTomEnvVars Platform-neutral configuration map.
Default fallback TomFallbackPlatformUtils Console output works; detection throws until configured.

Runtime environments and platforms

CapabilityType / memberNotes
Environment model TomEnvironment Named, with optional parent + initializer.
Platform model TomPlatform Named target with an initializer hook.
Runtime registry TomRuntime Holds current env / platform + hierarchy resolution.
Platform constants platformWeb platformMacos platformWindows platformLinux platformAndroid platformIos platformFuchsia Predefined targets.
Env constants defaultTomEnvironment noTomEnvironment Defaults / sentinels.

---

Quick start

import 'package:tom_basics/tom_basics.dart';

void main() {
  // The global logger is ready to use immediately.
  tomLog.setLogLevel(TomLogLevel.development);
  tomLog.info('Application starting...');

  try {
    throw TomBaseException('EXAMPLE_ERROR', 'Something went wrong');
  } on TomBaseException catch (e) {
    tomLog.error('Caught: ${e.key} - ${e.defaultUserMessage}');
    print('Exception UUID: ${e.uuid}'); // e.g. aceec92a-90ab-4f7b-896e-...
  }

  tomLog.info('Application finished.');
}

Running it prints (timestamps and the UUID vary per run):

2026-06-16 21:27:22.128328 - INFO    Application starting...   [main]
2026-06-16 21:27:22.140356 - ERROR   Caught: EXAMPLE_ERROR - Something went wrong   [main]
Exception UUID: aceec92a-90ab-4f7b-896e-595d7f1a94ca
2026-06-16 21:27:22.140927 - INFO    Application finished.   [main]

This is exactly example/tom_basics_example.dart — run it with dart run example/tom_basics_example.dart.

---

Example projects

SampleDemonstrates
example/tom_basics_example.dart The 12-line quick start above: logger + tracked exception.
tom_basics_sample The full exception-handling-with-UUID-tracking model, end to end, with the logger and platform seam. (article-grade sample, seven runnable examples)

---

Usage

Exception handling with UUID tracking

The constructor takes a key (a stable, machine-readable code) and a default user message (human-readable), with everything else optional:

import 'package:tom_basics/tom_basics.dart';

void main() {
  final ex = TomBaseException(
    'VALIDATION_ERROR',
    'The email address is not valid',
    parameters: {'field': 'email', 'value': 'not-an-email'},
  );

  print(ex.key);                  // VALIDATION_ERROR
  print(ex.defaultUserMessage);   // The email address is not valid
  print(ex.parameters?['field']); // email
  print(ex.uuid.length);          // 36  (a UUIDv4 string)
}

*Why a key and a message?* The key is what your code branches on and what you grep logs for; it never changes when you reword the prose. The defaultUserMessage is the fallback text shown when no localized message is available. Keeping them separate means translators and programmers never fight over the same string.

Correlating an error with a request. Pass requestUuid so a failure deep in a handler can be matched back to the inbound call that triggered it:

TomBaseException(
  'DB_TIMEOUT',
  'The database did not respond in time',
  requestUuid: incomingRequestId,
  parameters: {'table': 'orders', 'timeoutMs': 5000},
);

Wrapping a lower-level error. When you catch an exception and rethrow a domain-level one, keep the original in rootException and the original trace in stack so nothing is lost:

try {
  await db.query(sql);
} catch (e, s) {
  throw TomBaseException(
    'ORDER_LOAD_FAILED',
    'Could not load the order',
    rootException: e,
    stack: s,
  );
}

Inspecting the trace. Each exception folds out core/SDK frames at construction time and stores the result in stackTrace. Print it directly, or use printStackTrace():

try {
  throw TomBaseException('BOOM', 'demo');
} on TomBaseException catch (e) {
  e.printStackTrace();      // writes "<uuid>-<requestUuid> exception stacktrace:\n..."
  // or inspect the stored string:
  print(e.stackTrace.split('\n').first); // the first non-core frame
}

> Note. tom_basics deliberately stops at this minimal base class. The > full-featured exception type with integrated logging lives in > tom_core_kernel as TomException; reach for that when you are above the > foundation layer.

Logging

The global tomLog instance is usable without any setup. Each severity has its own method:

import 'package:tom_basics/tom_basics.dart';

void main() {
  tomLog.info('Server started on port 8080');
  tomLog.warn('Cache miss for key user:42');
  tomLog.error('Failed to connect to database');
  tomLog.debug('Request payload: {"id": 42}');
  tomLog.trace('Entering computeChecksum()');
}

Output lines carry the timestamp, isolate name, level, message, and the detected origin (the class.method that called the logger), e.g.:

2026-06-16 21:27:22.140 - INFO    Server started on port 8080   [main]

Log levels are bit patterns

A TomLogLevel is a bit mask. Individual levels can be combined with + and removed with -, and a logger only emits a message when its level matches the message level:

// Build a custom level: info plus errors, nothing else.
var quiet = TomLogLevel.info + TomLogLevel.errors;
tomLog.setLogLevel(quiet);

tomLog.info('shown');      // matches -> printed
tomLog.debug('hidden');    // no overlap -> filtered out
tomLog.error('shown');     // matches -> printed

// Subtract a level from a preset.
var prodNoInfo = TomLogLevel.production - TomLogLevel.info;
print(prodNoInfo.matches(TomLogLevel.info)); // false
print(prodNoInfo.matches(TomLogLevel.warn)); // true

Named presets cover the common cases:

PresetIncludes
developmenteverything, including trace
extendedproduction + debug + traffic
production info + warn + errors + status
stillwarn + errors + status
silenterrors + status only
offnothing

You can also resolve a level by name (useful for reading a level from config or an environment variable):

tomLog.setLogLevelByName('DEVELOPMENT');          // case-insensitive
final lvl = TomLogLevel.byName('SILENT');          // or null if unknown

Temporary verbosity with a level stack

To turn the volume up around one tricky section and restore it afterwards, push and pop:

tomLog.pushLogLevel(TomLogLevel.trace);
// ... noisy operations are fully traced here ...
tomLog.popLogLevel(); // back to whatever was active before

Per-class and per-method overrides

When stack-trace analysis is enabled (the default, TomLogger.globalSettingDetermineCaller == true), the logger detects which class.method emitted each message. That lets you raise verbosity for one location only:

// Trace everything in DatabaseService, debug one method of ApiClient.
tomLog.addNameLevel('DatabaseService', TomLogLevel.trace);
tomLog.addNameLevel('ApiClient.sendRequest', TomLogLevel.debug);

// Or configure several at once from a pattern string (e.g. from config):
tomLog.setLogLevelExceptions('DatabaseService=TRACE,ApiClient=DEBUG');

Redirecting output

The logger writes through logOutput, a TomLogOutput. The default is TomConsoleLogOutput (errors and status to stderr, everything else to stdout). Implement TomLogOutput.output to send logs anywhere:

class CollectingLogOutput extends TomLogOutput {
  final List<String> lines = [];

  @override
  void output(
    TomLogLevel loggerLevel,
    TomLogLevel logLevel,
    String level,
    Object message,
    String isolateName,
    DateTime timeStamp,
    String? origin,
  ) {
    if (logLevel.matches(loggerLevel)) {
      lines.add('$level ${convertToString(message)}');
    }
  }
}

void main() {
  final sink = CollectingLogOutput();
  tomLog.logOutput = sink;
  tomLog.info('captured');
  print(sink.lines); // [INFO    captured]
}

convertToString (provided by the base class) handles Strings, lazy Function messages, and TomLoggable objects automatically.

Custom log representation for your types

Implement TomLoggable so an object renders cleanly in logs without exposing internals:

class User implements TomLoggable {
  User(this.id, this.name);
  final String id;
  final String name;

  @override
  String get logRepresentation => 'User($id, $name)';
}

void main() {
  tomLog.info(User('42', 'Ada')); // logs: User(42, Ada)
}

Platform abstraction

Library code that must not import dart:io or dart:html directly talks to TomPlatformUtils.current. The concrete implementation is injected once at startup. Until you do that, the default TomFallbackPlatformUtils supports console output but throws UnimplementedError for detection — a loud, deliberate signal that the platform was never configured:

import 'package:tom_basics/tom_basics.dart';

// A Linux console/server implementation. Extending TomFallbackPlatformUtils
// gives working out/outError; we override every detection method so that
// TomRuntime.initializePlatform() (which probes them all) also works.
class ConsolePlatformUtils extends TomFallbackPlatformUtils {
  @override
  bool isDesktop() => true;
  @override
  bool isMobile() => false;
  @override
  bool isWeb() => false;
  @override
  bool isWindows() => false;
  @override
  bool isLinux() => true;
  @override
  bool isMacOs() => false;
  @override
  bool isFuchsia() => false;
  @override
  bool isAndroid() => false;
  @override
  bool isIos() => false;
}

void main() {
  TomPlatformUtils.setCurrentPlatform(ConsolePlatformUtils());

  final p = TomPlatformUtils.current;
  print(p.isDesktop()); // true
  print(p.isLinux());   // true
  p.out('hello from the platform seam'); // prints to stdout
}

A stub that overrides only isDesktop/isMobile/isWeb is enough for those three checks, but TomRuntime.initializePlatform() (below) probes the full set of OS methods — so a complete implementation like the one above is what makes detection work end to end. In real apps you don't write this yourself: tom_basics_console ships the desktop/server implementation and tom_core_flutter ships the Flutter one. The seam is what lets the same upstream code run on both.

The same singleton also hands out an HTTP client and a configuration map:

// Configuration that travels with the platform, not the call site.
TomPlatformUtils.envVars['API_BASE'] = 'https://api.example.com';
print(TomPlatformUtils.current.getTomEnvVars()['API_BASE']);
// https://api.example.com

// A platform-appropriate http client (once a real platform is configured):
// final client = TomPlatformUtils.current.httpClient();
// final res = await client.get(Uri.parse('https://api.example.com/health'));

Runtime environments and platforms

The runtime model answers "which environment and platform are active, and what is the chain of fallbacks?" Register environments, pick the current one, and walk the hierarchy:

import 'package:tom_basics/tom_basics.dart';

void main() {
  final prod = TomRuntime.addEnvironment(const TomEnvironment('production'));
  TomRuntime.addEnvironment(
    TomEnvironment('staging', parent: prod),
  );

  TomRuntime.setCurrentEnvironment('staging');
  print(TomRuntime.getCurrentEnvironment().env); // staging

  // Root-to-current chain, used by higher layers to resolve fallbacks.
  final chain = TomRuntime.getEnvironmentHierarchy().map((e) => e.env).toList();
  print(chain); // [production, staging]
}

An environment may carry an initializer that runs when it is activated:

final dev = TomEnvironment(
  'development',
  isDevelopment: true,
  initializer: (env) => tomLog.info('Activated ${env.env}'),
);
dev.initialize(); // logs: Activated development

Platform detection ties the platform seam to the runtime registry. Once a real TomPlatformUtils is configured, initializePlatform() detects and records the current platform:

TomPlatformUtils.setCurrentPlatform(ConsolePlatformUtils());
TomRuntime.setCurrentEnvironment('production');
TomRuntime.initializePlatform();
print(TomRuntime.printReport());
// TomRuntime: Platform TomPlatform: linux Root Environment ... Current Environment ...

---

Architecture

tom_basics is four small, independent modules behind a single barrel export (lib/tom_basics.dart):

                       package:tom_basics/tom_basics.dart
                                     │  (barrel export)
        ┌──────────────┬─────────────┼──────────────────┐
        │              │             │                  │
 ┌──────┴──────┐ ┌─────┴──────┐ ┌────┴─────────┐ ┌──────┴────────────┐
 │ exceptions/ │ │ logging/   │ │ runtime/     │ │ runtime/          │
 │ exception_  │ │ logging.   │ │ platform_    │ │ platform_         │
 │ base.dart   │ │ dart       │ │ neutral.dart │ │ environment_      │
 │             │ │            │ │              │ │ runtime.dart      │
 │ TomBase     │ │ TomLogger  │ │ TomPlatform  │ │ TomEnvironment    │
 │ Exception   │ │ TomLogLevel│ │ Utils        │ │ TomPlatform       │
 │             │ │ TomLogOut  │ │ (+ fallback) │ │ TomRuntime        │
 └─────────────┘ └─────┬──────┘ └──────┬───────┘ └───────────────────┘
   uuid,                │  uses         │ uses
   stack_trace          └───────────────┘
                     (logging routes its console
                      output through the platform seam)

The only internal coupling is that logging writes through the platform seam (TomConsoleLogOutput calls TomPlatformUtils.current.out/outError) and reads the isolate name from it. Exceptions and the environment model stand alone.

Key types

TypeResponsibility
TomBaseException Minimal exception base with UUID, timestamp, params, cause, and folded stack trace.
TomLogger The logger: severity methods, level + per-name configuration, output dispatch.
tomLogThe global TomLogger instance.
TomLogLevel Bitwise log-level value with + , - , matches , and named presets.
TomLogOutput Abstract log sink; TomConsoleLogOutput is the default stdout/stderr impl.
TomLoggable Interface for objects that supply their own logRepresentation.
TomPlatformUtils Injectable platform seam: detection, console output, HTTP client, env vars.
TomFallbackPlatformUtils Default impl: console output works, detection throws until configured.
TomEnvironment A named runtime environment with optional parent + initializer.
TomPlatformA named target platform with an initializer hook.
TomRuntime Registry of current environment/platform and hierarchy resolution.

---

Ecosystem

tom_basics sits at the root of the basics layer; the rest of the framework imports it, never the other way around.

                      ┌───────────────────────────┐
                      │  tom_core_kernel / server  │   higher-level Tom packages
                      │  tom_core_flutter, d4rt …  │   (TomException, DI, …)
                      └─────────────┬──────────────┘
                                    │ depends on
        ┌────────────────┬──────────┼───────────────┐
        │                │          │               │
 ┌──────┴──────┐  ┌──────┴──────┐   │        ┌───────┴────────┐
 │ tom_basics_ │  │ tom_basics_ │   │        │  tom_crypto     │
 │ console     │  │ network     │   │        │  (uses logging) │
 │ (platform   │  │             │   │        └────────────────┘
 │  impl)      │  │             │   │
 └──────┬──────┘  └──────┬──────┘   │
        └────────────────┴──────────┘
                         │ all depend on
                  ┌──────┴───────┐
                  │  tom_basics  │  ← you are here
                  └──────────────┘
            (uuid · stack_trace · http only)

See the basics repository map for the full package catalogue and the samples learning path.

---

Further documentation

TomPlatformUtils implementation that backs the platform seam. - tom_crypto — a downstream consumer that logs through tomLog. - tom_core_kernel (separate repo) — provides TomException, the full-featured exception type that builds on this base.

---

Status

  • Version: 1.0.0
  • SDK: Dart ^3.10.0
  • Tests: 6 passing (TomBaseException group — construction, parameters,

custom UUID, cause capture, toString, stack-trace capture). Run with dart test or testkit :test. - Analyzer: clean (dart analyze → no issues).

License
BSD 3-Clause License

Copyright (c) 2024-2026, Peter Nicolai Alexis Kyaw
Find me on LinkedIn under Alexis Kyaw
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.