This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Chronicle Analytics is a lightweight telemetry client library for sending usage events to Google Analytics (both GA3/Universal Analytics and GA4). It is designed as a zero-dependency Java library with minimal footprint, thread-safe operation, and best-effort async delivery.
Key characteristics:
- Zero transitive runtime dependencies (only
org.jetbrains:annotationsat compile time) - Thread-safe with single daemon thread for HTTP operations
- Fire-and-forget async event sending (never blocks caller)
- Automatic JUnit detection to prevent test events
- Built-in frequency limiting and anti-storm protection
- OSGi bundle support
# Verify build with all tests
mvn clean verify
# Quick compile (skip tests)
mvn clean compile -DskipTests
# Run specific test
mvn test -Dtest=AnalyticsTest
# Check code coverage (requires Java 11+)
mvn clean test jacoco:report
# Report at: target/site/jacoco/index.html
# Install to local repository
mvn clean installCoverage requirements:
- Line coverage: 89%
- Branch coverage: 72%
Analytics instances are created via a fluent builder pattern:
Analytics analytics = Analytics.builder(measurementId, apiSecret)
.putEventParameter("app_version", "1.4.2")
.putUserProperty("os_name", "Linux")
.withFrequencyLimit(10, 1, TimeUnit.MINUTES)
.withErrorLogger(System.err::println)
.build();
analytics.sendEvent("started");Public API (src/main/java/net/openhft/chronicle/analytics/):
Analyticsinterface: Main public API withsendEvent()methodsAnalytics.Builderinterface: Fluent configuration API
Internal Implementation (src/main/java/net/openhft/chronicle/analytics/internal/):
VanillaAnalyticsBuilder: Concrete builder, auto-selects GA3 vs GA4 based on measurement ID prefixAbstractGoogleAnalytics: Base class handling frequency limiting, client ID persistence, anti-storm logicGoogleAnalytics4: GA4 implementation (JSON payloads to/mp/collect)GoogleAnalytics3: Legacy Universal Analytics (URL-encoded to/collect)MuteAnalytics: Null-object pattern for testing (automatic when JUnit detected)HttpUtil: Single-threaded executor for async HTTP POST with 2-second timeoutFilesUtil: Client ID persistence in~/.chronicle.analytics.client.idJsonUtil: Lightweight JSON serialization (no external library)JUnitUtil: Detects JUnit 4/5 on classpath
- Zero Dependencies: Custom JSON implementation and native HTTP client to avoid transitive dependencies
- Best-Effort Delivery: No retries, no delivery guarantees - analytics should never impact application reliability
- Frequency Limiting: Built-in rate throttling prevents excessive traffic
- Anti-Storm Protection: Filesystem timestamp prevents analytics storms when many instances start simultaneously
- Automatic JUnit Detection: Mutes analytics when tests are running (override with
withReportDespiteJUnit()) - Single HTTP Thread: Daemon thread
chronicle~analytics~http~clienthandles all HTTP operations - Dual Protocol Support: Automatically selects GA3 (UA-* prefix) or GA4 based on measurement ID
- Client ID Persistence: Stable UUID in user home directory tracks returning users
- Analytics instances: Thread-safe (atomic operations for rate limiting)
- Builder: Not thread-safe (single-use pattern enforced)
- HTTP sending: Single dedicated daemon thread
- File operations: Lock-free, best-effort
- Client calls
analytics.sendEvent(name, params) - Eligibility check: mute detection, frequency limit
- Payload assembly: merge parameters, load client ID
- Async dispatch: submit HTTP POST task to executor
- Fire-and-forget: return immediately, log errors to error logger
CRITICAL: All code and documentation must follow these rules:
- British English spelling:
organisation,licence(notorganization,license) except technical terms likesynchronized - ISO-8859-1 only: Code points 0-255. No smart quotes, non-breaking spaces, or accented characters except in string literals
- Use textual forms for unavailable symbols:
micro-second,>=,:alpha:
Only write JavaDoc that adds information beyond the method signature:
DO:
- State behavioural contracts, edge cases, thread-safety guarantees
- Document units, performance characteristics, checked exceptions
- Keep first sentence short (becomes summary line)
DON'T:
- Restate the obvious ("Gets the value", "Sets the name")
- Duplicate parameter names/types without additional explanation
- Leave stale comments that contradict code
- Subject line <= 72 chars, imperative mood: "Fix roll-cycle offset in ExcerptAppender"
- Reference JIRA/GitHub issue if exists
- Body: root cause → fix → measurable impact
- Run
mvn verifyafter rebasing
- Framework: JUnit 5 (Jupiter)
- Parallel execution: 4 forks with JVM reuse
- Convention: Test classes end with
Test.java, mirror package structure - Integration tests: Use MockWebServer for HTTP endpoint testing
- File cleanup: Tests clean up
.chronicle.analytics.*files in home directory
- Unit tests: Direct class testing (FilesUtilTest, JsonUtilTest)
- Integration tests: End-to-end with MockWebServer (AnalyticsTest, GoogleAnalytics3Test)
- Example mains: Runnable examples (AnalyticsExampleMain)
Modify VanillaAnalyticsBuilder to add builder methods, then update GoogleAnalytics4.jsonFor() and GoogleAnalytics3.bodyFor() to include them in payloads.
Edit AbstractGoogleAnalytics.attemptToSend() to adjust frequency limit logic.
Modify HttpUtil class (connection timeout, thread pool, error handling).
Update JsonUtil for additional character escaping or structure changes.
src/main/java/net/openhft/chronicle/analytics/
├── Analytics.java # Public API interface
├── internal/
│ ├── VanillaAnalyticsBuilder.java # Builder implementation
│ ├── AbstractGoogleAnalytics.java # Base implementation
│ ├── GoogleAnalytics4.java # GA4 implementation
│ ├── GoogleAnalytics3.java # UA implementation
│ ├── MuteAnalytics.java # Null object
│ ├── HttpUtil.java # HTTP client
│ ├── FilesUtil.java # File I/O
│ ├── JsonUtil.java # JSON serialization
│ └── JUnitUtil.java # Test detection
src/test/java/net/openhft/chronicle/analytics/
├── AnalyticsTest.java # API integration tests
├── internal/
│ ├── GoogleAnalytics3Test.java # GA3 tests
│ ├── GoogleAnalyticsTest.java # Core tests
│ ├── FilesUtilTest.java # File utils tests
│ └── ...
src/main/adoc/
├── project-requirements.adoc # Formal requirements
└── security-review.adoc # Security analysis
src/main/java/net/openhft/chronicle/analytics/Analytics.java: Main public APIsrc/main/java/net/openhft/chronicle/analytics/internal/VanillaAnalyticsBuilder.java: Builder and factory logicsrc/main/java/net/openhft/chronicle/analytics/internal/AbstractGoogleAnalytics.java: Rate limiting and eligibilitypom.xml: Maven configuration, dependencies, plugins
- GA4: Starts with
G-(e.g.,G-TDAZG4CU3G) - GA3/Universal Analytics: Starts with
UA-(e.g.,UA-123456-1)
Builder automatically selects implementation based on prefix.
- JUnit detection: Analytics automatically muted when JUnit 4/5 detected on classpath
- Client ID: Generated UUID stored in
~/.chronicle.analytics.client.id - Frequency limit: None by default (must explicitly configure)
- Thread name:
chronicle~analytics~http~client(daemon) - HTTP timeout: 2 seconds for connection and read
- Anti-storm file:
~/.chronicle.analytics.last(second-of-day timestamp)
putEventParameter(key, value): Add event-level parameterputUserProperty(key, value): Add user-level property (GA4 only)withFrequencyLimit(messages, duration, timeUnit): Rate limitingwithClientIdFileName(fileName): Custom client ID file locationwithUrl(url): Custom GA endpoint (e.g., debug endpoint)withErrorLogger(consumer): Custom error loggingwithDebugLogger(consumer): Custom debug loggingwithReportDespiteJUnit(): Override automatic JUnit muting
- Sensitive data: Caller must scrub sensitive information before calling
sendEvent() - Client ID file: Documents stable UUID at
~/.chronicle.analytics.client.id - Custom URL: Only override for trusted proxies
- HTTPS by default: Transport security enabled
- Error logging: Supply non-no-op error logger for production debugging
# Run Checkstyle (baseline ruleset)
mvn -Dcheckstyle.config.location=net/openhft/quality/checkstyle27/chronicle-baseline-checkstyle.xml \
-Dcheckstyle.skip=false checkstyle:check
# Run SpotBugs (Max effort, Low threshold)
mvn -q -Dspotbugs.effort=Max -Dspotbugs.threshold=Low \
com.github.spotbugs:spotbugs-maven-plugin:spotbugsStatus: Chronicle-Analytics maintains zero Checkstyle and SpotBugs violations.
- java11: Excludes service files for Java 11+ JPMS builds
- quality: Runs Checkstyle and SpotBugs (activated on Java 11+)
The library is built as an OSGi bundle with:
- Bundle-SymbolicName:
net.openhft.chronicle-analytics - Multi-Release manifest entry for Java 9+ modules
- Optional import:
software.chronicle.enterprise.analytics
Applications can disable Chronicle-Analytics via:
<dependency>
<groupId>net.openhft</groupId>
<artifactId>chronicle-analytics</artifactId>
<version>0.EMPTY</version>
</dependency>configurations {
implementation {
exclude group: 'net.openhft', module: 'chronicle-analytics'
}
}- Parent:
net.openhft:java-parent-pom:1.27ea1 - BOM:
net.openhft:third-party-bom:3.27ea7 - Version:
2.27ea2-SNAPSHOT
When making changes:
- Update relevant
.adocfiles insrc/main/adoc/alongside code changes - Keep documentation, tests, and code synchronised
- Small commits: one requirement or coherent change per commit
- Documentation should be precise enough for clean-room re-implementation
- JavaDocs: https://www.javadoc.io/doc/net.openhft/chronicle-analytics
- Maven Central: https://maven-badges.herokuapp.com/maven-central/net.openhft/chronicle-analytics
- GitHub Issues: https://github.com/OpenHFT/Chronicle-Analytics/issues
- Nexus Repository: https://nexus.chronicle.software/