Configuration
Declaring services with .dstl8.yaml, excluding files with .dstl8ignore, and language coverage
Two optional files at the repo root configure extraction: .dstl8.yaml declares the services a repo builds, and .dstl8ignore excludes paths. Neither is required — service detection and the built-in excludes cover the common cases.
Declaring services: .dstl8.yaml
Services are how log sites map to what's actually running: a pattern link that says service checkout emits this line from payments/client.go:212 is only as good as the repo→service mapping. The extractor detects services automatically, first match wins per service name, in this order:
Precedence
Source
Where it looks
source tag in the artifact
1
.dstl8.yaml
services: stanza at the repo root
dstl8_yaml
2
Helm
charts/**/values.yaml
helm
3
Kubernetes manifests
k8s/**/*.y{a,}ml, deploy/**/*.y{a,}ml — app.kubernetes.io/name labels, app: labels, Deployment names
k8s
4
Compose / Procfile
docker-compose*.y{a,}ml service keys, Procfile process names
compose
5
Fallback
the repo's short name as the single service (only when nothing else matched)
inferred
Every service in the artifact carries its source tag, so Möbius knows how much to trust the mapping. If detection gets it wrong — or a monorepo builds several services from different subtrees — declare them explicitly:
# .dstl8.yaml
services:
- name: checkoutWith no paths, the whole repo maps to the service. This is mainly useful when the deployed service name differs from the repo name and there are no manifests to detect it from.
# .dstl8.yaml
services:
- name: checkout
paths: ["cmd/checkout/**", "payments/**"]
- name: checkout-worker
paths: ["cmd/worker/**"]
- name: admin-api
paths: ["services/admin/**"]paths are glob patterns relative to the repo root; a log site maps to the service whose globs cover its file.
name is required for every entry. A malformed .dstl8.yaml is a hard error — it's the explicit override, so it fails loudly instead of silently falling back to detection. The --service name=glob CLI flag sits at the same trust tier: it wins over any detected or declared mapping with the same name, and other detected services are kept.
Excluding files: .dstl8ignore
The extractor never reads a file you've told git to ignore, and .dstl8ignore excludes more on top. Exclusions are evaluated in this order:
.gitignore— always respected: files are enumerated withgit ls-files, so only git-tracked files are ever scanned. Untracked and ignored files never enter the pipeline..dstl8ignore— extra excludes, at the repo root.Built-in excludes (below).
Test sources (below) — unless you pass
--include-tests.--excludeflags onbuild/push.
.dstl8ignore uses gitignore pattern syntax:
one pattern per line; blank lines are skipped
#starts a comment line!re-includes something a previous pattern excluded — the last matching rule winsa trailing
/matches directories only (and everything beneath them)*,?, and[...]match within one path segment;**crosses segmentsa pattern containing
/is anchored to the repo root; a bare name matches at any depth
A worked example — keeping generated code out of the graph:
Excluded files disappear from the artifact entirely — they're not in the file inventory, and their log sites are never extracted. Verify the effect with dstl8 graph build --dry-run | jq '.files[].path'.
Built-in excludes
Applied after .dstl8ignore, always on:
Directories at any depth:
vendor/,node_modules/,third_party/,dist/,build/,testdata/Generated/minified file suffixes:
*.min.js,*_pb.go,*.gen.goFiles larger than 1 MB (counted in
stats.skipped_files)Binary files (null byte in the first 8 KB)
There's no way to re-include a built-in exclude — vendored and generated code stays out by design.
Test sources
Test files are excluded by default. A log statement inside a test never runs in production, so a runtime pattern that matches one is a false attribution — Dstl8 would place a live error in a fixture instead of in the code that actually emitted it.
Detection is by path, following each language's conventions:
Directories (any depth)
test/, tests/, spec/, specs/, __tests__/, __mocks__/, test_suite/ — this also covers Maven/Gradle's src/test/…
Go
*_test.go
Python
test_*.py, *_test.py, *_tests.py
JavaScript / TypeScript
*.test.js, *.spec.js and the .jsx, .mjs, .cjs, .ts, .tsx variants
Ruby
*_spec.rb, *_test.rb
Java / Kotlin
*Test.java, *Tests.java, *Test.kt, *Tests.kt
C#
*Test.cs, *Tests.cs
PHP
*Test.php, *Tests.php
Rust
*_test.rs, plus the crate's tests/ directory
Elixir
*_test.ex, *_test.exs
Files that merely look like tests are kept — internal/testing/harness.go, pkg/attestation/verify.go and services/latest/handler.go all stay in the graph.
Tests inside a production file (Rust's #[cfg(test)] mod tests, a nested JUnit class) can't be detected by path and are still indexed. If those produce noisy matches, exclude the file with .dstl8ignore.
dstl8 graph build reports the count so the effect is never invisible:
Pass --include-tests on build/push to index them anyway — useful if your tests emit the same log statements you're trying to trace, or if you want deploy diffs to include test-only changes.
Language coverage
Every text file that survives the walk appears in the file inventory (path, language, line count, content hash). What else is extracted depends on the language:
Go
Go compiler AST
Yes (confidence: extracted)
Yes — functions, methods, types; import edges; go.mod deps
Python, JavaScript, TypeScript, TSX, Java, Ruby, C#, Rust, Kotlin, PHP
tree-sitter grammars
Yes (confidence: extracted)
Yes — symbols where the grammar supports definition tagging; per-language import extraction
Everything else with a recognizable syntax (~250 languages)
lexer token-scan heuristic
Yes, heuristic (confidence: inferred)
No
Shell, YAML, JSON, Markdown
none (inventory-only)
No
No
Notes on coverage:
Log-site fidelity: full parsers find log calls from the syntax tree — logger calls (
log.Error,logger.warning,console.error,tracing::error!, …), error constructors (fmt.Errorf,errors.New), and any call whose message argument is a string literal of three or more words. Format-string holes (%s,{},${…}, f-string expressions) become wildcards in the normalized template.Non-literal messages (a message built entirely at runtime) keep the site — file, line, function, severity — with an empty template and
confidence: ambiguous. Still useful: "this function logs at error level."Heuristic languages get log sites only, tagged
confidence: inferred, from a lexer token scan (logger-looking callee, open paren, string literal). No symbols, no import edges.Language detection is by file extension first, then well-known basenames (
Rakefile,Gemfile), then the shebang line (#!/usr/bin/env python3and friends). Files with an unknown extension still get the heuristic scan.External dependencies come from manifests, not source:
go.mod,package.json,requirements.txt,pyproject.toml,Gemfile,Cargo.toml,pom.xml,build.gradle/build.gradle.kts, and*.csproj.
--no-symbols turns off the symbols/imports/external-deps layer entirely, for teams that want the artifact limited to the file inventory and log sites — see the CLI reference.
Last updated