Skip to main content

Understanding CLI usage across your organization

As developers across your organization adopt the Moderne CLI, understanding usage patterns becomes critical for maximizing value. Without visibility into who's using the tool, which recipes are being run and committed, and where developers encounter friction, you can't identify adoption gaps, measure impact, or provide targeted support.

To help you gather this information, the Moderne CLI automatically generates telemetry in JSON and CSV files, capturing details about builds, recipe runs, and git operations. You can then analyze these files locally or aggregate them centrally to monitor CLI usage across your organization.

In this guide, we'll walk you through how these telemetry files work and how you can collect and analyze them.

How telemetry is generated

The Moderne CLI generates telemetry files whenever you run commands (e.g., mod build, mod run, etc.). These files will be created in the .moderne directory in each repository.

Here's a typical workflow where each step will generate telemetry data:

Repository-level telemetry

When you run a CLI command, telemetry is captured in each repository's .moderne/<command>/trace.json file.

For the sync command, this file looks like:

{
"clone": {
"success": true,
"cloneUri": "https://github.com/company/repository",
"startTime": "2025-09-29T14:17:31.477727-04:00",
"endTime": "2025-09-29T14:17:35.101962-04:00",
"log": "file:///.../.moderne/sync/sync.log",
"changeset": "452d7ca8902e7338e999290eebad9062c342915f",
"elapsedTimeMs": 3624
},
"org": "ALL/Company/Team",
"repository": {
"origin": "github.com",
"path": "company/repository",
"branch": "main",
"partition": null
}
}

As you run subsequent commands, each trace.json file will include telemetry from the previous steps. This lets you trace failures back to their source.

Example of a trace.json for a recipe run
{
"run": {
"outcome": "Succeeded",
"startTime": "2025-10-02T14:37:20.68234-04:00",
"endTime": "2025-10-02T14:37:34.715203-04:00",
"log": "file:///Users/matt/workspaces/app.moderne.io/Default/awslabs/aws-saas-boost/.moderne/run/20251002143646-F5Tor/run.log",
"id": "20251002143646-F5Tor",
"unlicensedAttempt": false,
"streaming": false,
"recipeId": "org.openrewrite.maven.DependencyVersionsToPropertiesDefault",
"recipeInstanceName": "Extract dependency versions to properties with default naming",
"recipeOptions": {},
"recipeArtifact": "org.openrewrite.maven:maven-properties-recipes:0.1.0-SNAPSHOT",
"estimatedEffortTimeSavingsMs": 13200000,
"dependencyResolutionTimeMs": 0,
"filesWithFixResults": 44,
"filesWithSearchResults": 44,
"filesWithErrors": 0,
"filesSearched": 799,
"dataTables": 2,
"elapsedTimeMs": 14033
},
"build": {
"outcome": "Succeeded",
"startTime": "2025-10-02T14:47:51.472025Z",
"endTime": "2025-10-02T14:48:43.610105Z",
"log": "file:///.../.moderne/build/20251002104715-wvwCu/build.log",
"id": "20251002104715-wvwCu",
"changeset": "452d7ca8902e7338e999290eebad9062c342915f",
"dependencyResolutionTimeMs": 516560,
"mavenVersion": "3.9.10",
"gradleVersion": null,
"bazelVersion": null,
"dotnetVersion": null,
"pythonVersion": null,
"nodeVersion": null,
"sourceFileCount": 799,
"lineCount": 147205,
"parseErrorCount": 25,
"weight": 640108,
"maxWeight": 65828,
"maxWeightSourceFile": "client/web/yarn.lock",
"elapsedTimeMs": 52138
},
"clone": {
"outcome": null,
"cloneUri": "https://github.com/company/repository",
"startTime": "2025-09-29T14:17:31.477727-04:00",
"endTime": "2025-09-29T14:17:35.101962-04:00",
"log": "file:///.../.moderne/sync/sync.log",
"changeset": "452d7ca8902e7338e999290eebad9062c342915f",
"elapsedTimeMs": 3624
},
"org": "ALL/Company/Team",
"repository": {
"origin": "github.com",
"path": "company/repository",
"branch": "main",
"partition": null
}
}
note

The run command's trace.json includes telemetry for the clone and build blocks from earlier steps.

trace.json schema

The following tables describe the trace.json schema, including metadata common to all commands and fields specific to each command type:

Repository and organization metadata

Every trace.json file includes repository and organization metadata to identify where actions occurred:

FieldTypeDescription
orgstringOrganizational hierarchy (e.g., ALL/Default)
repository.originstringSource control platform (e.g., github.com)
repository.pathstringRepository path (e.g., apache/maven-doxia)
repository.branchstringBranch name (e.g., master, main)
repository.partitionstring/nullRepository partition if applicable, null otherwise

Common metadata

All commands contain the following metadata for that specific command's run:

FieldTypeDescription
outcomestringCommand outcome (e.g., Succeeded, Failed)
startTimestringISO 8601 timestamp when the command started
endTimestringISO 8601 timestamp when the command completed
logstringFile URI to the log file for the output of this specific command run
elapsedTimeMsnumberDuration of the command in milliseconds

All commands except mod git sync include a unique identifier for tracking multiple runs:

FieldTypeDescription
idstringUnique identifier for this command run

Clone command fields

When you sync repositories with mod git sync, a clone block is created with these fields:

FieldTypeDescription
successbooleanWhether the clone operation succeeded
cloneUristringThe URI used to clone the repository
changesetstringThe git commit SHA that was checked out

Build command fields

When you build LSTs with mod build, a build block is created with these fields:

FieldTypeDescription
changesetstringThe git commit SHA that was built
dependencyResolutionTimeMsnumberTime spent resolving dependencies in milliseconds
mavenVersionstring/nullMaven version if Maven project, null otherwise
gradleVersionstring/nullGradle version if Gradle project, null otherwise
bazelVersionstring/nullBazel version if Bazel project, null otherwise
dotnetVersionstring/null.NET version if .NET project, null otherwise
pythonVersionstring/nullPython version if Python project, null otherwise
nodeVersionstring/nullNode version if Node project, null otherwise
sourceFileCountnumberTotal number of source files parsed
lineCountnumberTotal lines of code across all source files
parseErrorCountnumberNumber of files that failed to parse
weightnumberCombined weight of all source files
maxWeightnumberWeight of the largest source file
maxWeightSourceFilestringPath to the largest source file

Run command fields

When you run recipes with mod run, a run block is created with these fields:

FieldTypeDescription
unlicensedAttemptbooleanWhether this was run without a license
streamingbooleanWhether streaming mode was used
recipeIdstringFully qualified ID of the recipe that was run
recipeInstanceNamestringHuman-readable name of the recipe
recipeOptionsobjectMap of recipe options/parameters used
recipeArtifactstringMaven coordinates of the recipe artifact
estimatedEffortTimeSavingsMsnumberEstimated developer time saved in milliseconds
dependencyResolutionTimeMsnumberTime spent resolving dependencies in milliseconds
filesWithFixResultsnumberNumber of files with changes applied
filesWithSearchResultsnumberNumber of files matching search criteria
filesWithErrorsnumberNumber of files that encountered errors
filesSearchednumberTotal number of files searched
dataTablesnumberNumber of data tables generated

Apply command fields

When you apply changes with mod git apply, an apply block is created with the common metadata fields. There are currently no additional telemetry fields for this command.

Commit command fields

When you commit changes with mod git commit, a commit block is created with these fields:

FieldTypeDescription
branchstringThe branch into which these changes were committed

Push command fields

When you push changes with mod git push, a push block is created with these fields:

FieldTypeDescription
remoteBranchstringThe remote branch into which changes are pushed
setUpstreambooleanTrue if you set a specific upstream during this push, false otherwise

Organization-level telemetry

The Moderne CLI is designed to operate against many repositories simultaneously. Because of this, in addition to creating repository-specific trace.json files, it generates an aggregate trace.csv file in the .moderne/<command> directory. This CSV contains the same data as the individual JSON files, with each row representing a repository and each column representing a field.

This CSV is also copied to $MODERNE_HOME/cli/trace, making it easy to examine and share telemetry across all runs in a centralized location:

Per-Repository Files:          Aggregate File:
repo1/.moderne/build/trace.json ─┐
repo2/.moderne/build/trace.json ─┼─> .moderne/build/trace.csv ─> $MODERNE_HOME/cli/trace/
repo3/.moderne/build/trace.json ─┘

Note that these centralized CSV files do not include log field paths, as those are specific to your local environment.

Collecting results in a central location

Many organizations use centralized observability and business intelligence (BI) tools to monitor developer workflows and measure productivity initiatives. The Moderne CLI's telemetry is designed to integrate seamlessly with these systems.

As mentioned earlier, the CLI automatically aggregates CSV files to the $MODERNE_HOME/cli/trace directory. These files are ready to be ingested by your existing BI tools - allowing you to track CLI usage, recipe adoption, and impact across your entire organization.

You can publish these files with every command run, or you can collect them on a scheduled cadence.

Wrapping the CLI to publish telemetry

Due to the fact every organization's BI system is different, we strongly recommend that you wrap the Moderne CLI in a script that handles telemetry publishing and any necessary data transformation.

A wrapper script allows you to:

  • Run pre- and post-processing steps
  • Publish telemetry to your BI endpoint
  • Transform data to match your system's requirements

Here's a basic wrapper script template:

mod.sh
# Main execution
main() {
# Extract the first command argument (e.g., "build" from "mod.sh build .")
local command_name="$1"

if [[ -f "$MOD_JAR" ]]; then
# Execute the Moderne CLI jar with provided arguments
java -jar "$MOD_JAR" "$@"
CLI_EXIT_CODE=$?
elif command -v mod &> /dev/null; then
# Execute the Moderne CLI binary
mod "$@"
CLI_EXIT_CODE=$?
else
echo -e "Error: Moderne CLI not found at $MOD_JAR" >&2
echo "Please set the correct path to the Moderne CLI JAR file" >&2
exit 1
fi

# Add a newline after mod output
echo >&2

# TODO: Run any post-processing work here now that the Moderne CLI command is completed

# Exit with the same code as the CLI
exit $CLI_EXIT_CODE
}

# Run main function
main "$@"
Example of a mod.sh that publishes telemetry to an API endpoint
mod.sh
# Function to publish telemetry data
publish_telemetry() {
local command_name="$1"

# Skip if no command name provided
if [[ -z "$command_name" ]]; then
return 0
fi

if [[ ! -d "$TELEMETRY_DIR" ]]; then
return 0
fi

# Look for CSV files under the command subdirectory
local search_dir="$TELEMETRY_DIR/$command_name"
if [[ ! -d "$search_dir" ]]; then
return 0
fi

# Find all CSV files in the search directory and subdirectories recursively
# Using find for compatibility with older bash versions (macOS default is 3.2)
CSV_FILES=()
while IFS= read -r -d '' file; do
CSV_FILES+=("$file")
done < <(find "$search_dir" -name "*.csv" -type f -print0 2>/dev/null)

if [[ ${#CSV_FILES[@]} -eq 0 ]]; then
return 0
fi

echo "Publishing telemetry data to $BI_ENDPOINT..." >&2

for csv_file in "${CSV_FILES[@]}"; do
if [[ -f "$csv_file" ]]; then
parent_dir="$(dirname "$csv_file")"
# Get relative path from current directory
relative_path="${csv_file#$(pwd)/}"
echo "Processing: $relative_path" >&2
# Only attempt to publish if endpoint is configured
if [[ -n "$BI_ENDPOINT" ]]; then
# Build curl command with optional parameters
CURL_CMD=(curl -X POST -H "Content-Type: text/csv" --data-binary "@$csv_file")

# Add basic authentication if configured
if [[ -n "$BI_AUTH_USER" && -n "$BI_AUTH_PASS" ]]; then
CURL_CMD+=(--user "$BI_AUTH_USER:$BI_AUTH_PASS")
fi

# Add proxy configuration if configured
# Determine which proxy to use based on endpoint URL
PROXY_URL=""
if [[ "$BI_ENDPOINT" == https://* && -n "$HTTPS_PROXY" ]]; then
PROXY_URL="$HTTPS_PROXY"
elif [[ -n "$HTTP_PROXY" ]]; then
PROXY_URL="$HTTP_PROXY"
fi

if [[ -n "$PROXY_URL" ]]; then
CURL_CMD+=(--proxy "$PROXY_URL")

# Add proxy authentication if configured
if [[ -n "$PROXY_USER" && -n "$PROXY_PASS" ]]; then
CURL_CMD+=(--proxy-user "$PROXY_USER:$PROXY_PASS")
fi
fi

# Add endpoint and common options
CURL_CMD+=("$BI_ENDPOINT" --silent --fail --show-error)

# Execute curl command
ERROR_MSG=$("${CURL_CMD[@]}" 2>&1)

if [[ $? -eq 0 ]]; then
# Delete parent directory on successful post (e.g., 20250822153915-gemmm/)
rm -rf "$parent_dir"
echo -e "${GREEN}[OK] Published: $relative_path${NC}" >&2
else
echo -e "${YELLOW}[WARN] Failed to publish: $relative_path${NC}" >&2
echo -e "${YELLOW} Error: $ERROR_MSG${NC}" >&2
fi
else
echo -e "${YELLOW}Note: Telemetry endpoint not configured. Skipping: $relative_path${NC}" >&2
fi
fi
done
}

# Main execution
main() {
# Extract the first command argument (e.g., "build" from "mod.sh build .")
local command_name="$1"

if [[ -f "$MOD_JAR" ]]; then
# Execute the Moderne CLI jar with provided arguments
java -jar "$MOD_JAR" "$@"
CLI_EXIT_CODE=$?
elif command -v mod &> /dev/null; then
# Execute the Moderne CLI binary
mod "$@"
CLI_EXIT_CODE=$?
else
echo -e "Error: Moderne CLI not found at $MOD_JAR" >&2
echo "Please set the correct path to the Moderne CLI JAR file" >&2
exit 1
fi

# Add a newline after mod output
echo >&2

# Publish telemetry data after CLI execution, passing the command name
publish_telemetry "$command_name"

# Exit with the same code as the CLI
exit $CLI_EXIT_CODE
}

# Run main function
main "$@"

Valuable metrics to monitor

Here are some key metrics that have proven valuable across many organizations, including Moderne itself. Note that this is not an exhaustive list - your organization may have additional metrics specific to your initiatives:

Build Metrics:

  • Build success rate
  • Build duration over time
  • Builds by tool
  • Builds over time
  • Weight vs build time

Run Metrics:

  • Total recipes run
  • Recipe run success rate
  • Top recipes executed
  • Recipe runs over time
  • Total potential time saved by recipe, user, and teams

Commit Metrics:

  • Top recipes committed
  • Top users/teams committing recipe results
  • Top users/teams running recipes but not committing results
  • Most valuable recipes (Total time saved for recipes that end up in a commit)
  • Time from first run of a recipe to first commit on a repository