diff --git a/.github/workflows/check-deployed-package.yml b/.github/workflows/check-deployed-package.yml index e086219..80813b2 100644 --- a/.github/workflows/check-deployed-package.yml +++ b/.github/workflows/check-deployed-package.yml @@ -1,41 +1,35 @@ name: Check deployed package +# CI calls it after each release; run it by hand to check any version from any branch. on: + workflow_call: + inputs: + version: + type: string + required: true + secrets: # run.sh fails with a clear message when one is missing + LOGDASH_API_KEY: + required: false + LOGDASH_PERSONAL_API_KEY: + required: false workflow_dispatch: - workflow_run: - workflows: ['CI/CD Pipeline'] - types: - - completed + inputs: + version: + description: Version to check; empty checks the latest release + type: string + +permissions: + contents: read jobs: check-deployed-package: name: Check deployed package runs-on: ubuntu-latest - if: > - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.event == 'push' && - startsWith(github.event.workflow_run.head_branch, 'v') && - github.event.workflow_run.head_branch != github.event.workflow_run.repository.default_branch - permissions: - contents: read - packages: read - + timeout-minutes: 75 # up to an hour waiting for Maven Central steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Make run script executable - run: chmod +x check-deployed-package/run.sh - - - name: Verify environment variables - run: | - echo "GITHUB_ACTOR: ${{ github.actor }}" - echo "Has GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN != '' }}" - echo "Has LOGDASH_API_KEY: ${{ secrets.LOGDASH_API_KEY != '' }}" - - - name: Run LogDash demo + - uses: actions/checkout@v7 + - run: ./check-deployed-package/run.sh env: + LOGDASH_VERSION: ${{ inputs.version }} LOGDASH_API_KEY: ${{ secrets.LOGDASH_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_ACTOR: ${{ github.actor }} - run: ./check-deployed-package/run.sh \ No newline at end of file + LOGDASH_PERSONAL_API_KEY: ${{ secrets.LOGDASH_PERSONAL_API_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79f6326..f7add75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,31 +2,29 @@ name: CI/CD Pipeline on: push: - branches: [ main, dev ] - tags: [ 'v*' ] + branches: [ main ] pull_request: - branches: [ main, dev ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + # A release in progress must finish; a newer merge waits and releases after it. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: - contents: write - actions: read - checks: write - security-events: write - pull-requests: write + contents: read + +defaults: + run: + shell: bash # with pipefail, so a failing command in a pipe fails the step env: JAVA_VERSION: '17' MAVEN_OPTS: > - -Dmaven.repo.local=.m2/repository - -Xmx2048m - -XX:+UseG1GC + -Xmx2048m + -XX:+UseG1GC -Daether.connector.http.connectionMaxTtl=25 -Dmaven.artifact.threads=8 -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: validate: name: Code Quality & Security @@ -35,27 +33,29 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: 'temurin' + cache: maven - name: Validate Project - uses: nick-fields/retry@v3 + uses: nick-fields/retry@v4 with: timeout_minutes: 10 max_attempts: 2 retry_wait_seconds: 30 command: mvn clean validate compile -B --no-transfer-progress -DskipTests + # The quality profile's execution, which fails on a warning; a bare dependency:analyze only warns. - name: Verify Dependencies run: | - mvn dependency:analyze -B --no-transfer-progress -Pquality + mvn test-compile dependency:analyze-only@analyze-dependencies -B --no-transfer-progress -Pquality mvn dependency:tree -B --no-transfer-progress | tee dependency-tree.txt - name: Code Style Check (Spotless) @@ -65,7 +65,7 @@ jobs: run: mvn spotbugs:check -B --no-transfer-progress - name: Upload Dependency Tree - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: dependency-analysis path: dependency-tree.txt @@ -76,6 +76,10 @@ jobs: runs-on: ubuntu-latest needs: validate timeout-minutes: 25 + permissions: + contents: read + actions: read + checks: write # the test report strategy: fail-fast: false matrix: @@ -83,445 +87,175 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup JDK ${{ matrix.java }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: java-version: ${{ matrix.java }} distribution: 'temurin' + cache: maven - name: Run Tests & Coverage Check - uses: nick-fields/retry@v3 + uses: nick-fields/retry@v4 with: timeout_minutes: 20 max_attempts: 3 retry_wait_seconds: 30 command: mvn clean verify -B --no-transfer-progress -Djava.version=${{ matrix.java }} - - name: List test report files - if: always() && matrix.java == 17 - run: | - echo "Surefire reports:" - ls -l target/surefire-reports || true - echo "Failsafe reports:" - ls -l target/failsafe-reports || true - echo "JaCoCo reports:" - ls -l target/site/jacoco* || true - - name: Upload coverage reports to Codecov if: matrix.java == 17 - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 with: files: target/site/jacoco-merged/jacoco.xml,target/site/jacoco/jacoco.xml token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false + fail_ci_if_error: true - name: Upload Test Results - uses: dorny/test-reporter@v1 - if: always() && matrix.java == 17 + uses: dorny/test-reporter@v3 + if: ${{ !cancelled() && matrix.java == 17 }} with: name: Test Results (Java ${{ matrix.java }}) path: target/surefire-reports/TEST-*.xml,target/failsafe-reports/TEST-*.xml reporter: java-junit - fail-on-error: false + fail-on-error: false # the test step has failed the job already - build: - name: Build & Package + test-scripts: + name: Test Suite (release scripts) runs-on: ubuntu-latest - needs: [ validate, test ] - timeout-minutes: 15 - outputs: - version: ${{ steps.version.outputs.version }} - is-snapshot: ${{ steps.version.outputs.is-snapshot }} + timeout-minutes: 5 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - - name: Setup JDK - uses: actions/setup-java@v4 + - name: Setup Node + uses: actions/setup-node@v7 with: - java-version: ${{ env.JAVA_VERSION }} - distribution: 'temurin' + node-version: 24 - - name: Extract Version - id: version - run: | - VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "is-snapshot=$(echo $VERSION | grep -q SNAPSHOT && echo true || echo false)" >> $GITHUB_OUTPUT - - - name: Build Package - uses: nick-fields/retry@v3 - with: - timeout_minutes: 10 - max_attempts: 2 - retry_wait_seconds: 30 - command: mvn clean package -B --no-transfer-progress -DskipTests - - - name: Generate Documentation - run: mvn javadoc:javadoc -B --no-transfer-progress - - - name: Verify JAR Structure - run: | - jar -tf target/logdash-*.jar | head -20 - echo "JAR size: $(du -h target/logdash-*.jar | cut -f1)" - - - name: Upload Build Artifacts - uses: actions/upload-artifact@v4 - with: - name: build-artifacts-${{ steps.version.outputs.version }} - path: | - target/*-${{ steps.version.outputs.version }}.jar - !target/*-tests.jar - !target/original-*.jar - target/site/apidocs/** - retention-days: ${{ steps.version.outputs.is-snapshot == 'true' && 7 || 90 }} + - name: Run Tests + run: node --test 'scripts/*.test.ts' security-scan: name: Security Scanning runs-on: ubuntu-latest needs: validate timeout-minutes: 15 - if: > - (github.event_name == 'push' && github.ref == 'refs/heads/main') || - (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'security-scan')) || - startsWith(github.ref, 'refs/tags/v') + if: github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'security-scan') + permissions: + contents: read + security-events: write # the SARIF upload steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: 'temurin' + cache: maven - - name: Build for Security Scan - run: mvn clean compile -B --no-transfer-progress -DskipTests + # Online, Trivy fetches the dependencies' poms from Maven Central, which rate-limits it (429) and + # fails the scan. Maven puts them in ~/.m2 instead, where Trivy's offline scan finds them. + - name: Resolve Dependencies for Security Scan + run: for pom in pom.xml examples/*/pom.xml; do mvn dependency:tree -B --no-transfer-progress -q -f "$pom"; done - name: Run Trivy Scan - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' + env: + TRIVY_OFFLINE_SCAN: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@v3 - if: always() + if: hashFiles('trivy-results.sarif') != '' + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: 'trivy-results.sarif' - continue-on-error: true - github-release: - name: Release to GitHub Packages + # The version the merged commits call for; empty when none of them is a feat, fix, perf, or breaking change. + version: + if: github.event_name == 'push' runs-on: ubuntu-latest - needs: [ build, security-scan ] - timeout-minutes: 15 - if: startsWith(github.ref, 'refs/tags/v') - - permissions: - contents: write - packages: write - + outputs: + next: ${{ steps.next.outputs.version }} steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup JDK - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 with: - java-version: ${{ env.JAVA_VERSION }} - distribution: 'temurin' - - - name: Configure Maven Settings - uses: whelk-io/maven-settings-xml-action@v22 + fetch-depth: 0 # the tags and commits since the last release + - uses: actions/setup-node@v7 with: - servers: | - [ - { - "id": "github", - "username": "${env.GITHUB_ACTOR}", - "password": "${env.GITHUB_TOKEN}" - } - ] - - - name: Extract Version Info - id: version - run: | - VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - - # Validate version matches tag - if [[ "v$VERSION" != "${GITHUB_REF#refs/tags/}" ]]; then - echo "::error::Version mismatch: POM version ($VERSION) doesn't match tag (${GITHUB_REF#refs/tags/})" - exit 1 - fi - - - name: Check if version exists in GitHub Packages - run: | - VERSION=${{ steps.version.outputs.version }} - # Check if version already exists in GitHub Packages - if gh api /orgs/logdash-io/packages/maven/io.logdash.logdash/versions --jq '.[] | .name' | grep -q "^$VERSION$"; then - echo "::warning::Version $VERSION already exists in GitHub Packages" - echo "Either delete the existing version or bump the version in pom.xml" - else - echo "✅ Version $VERSION is available for release" - fi - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and Deploy to GitHub Packages - run: | - mvn clean deploy -B --no-transfer-progress \ - -Pgithub-release \ - -DperformRelease=true \ - -DskipTests=true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Notify GitHub Release Success + node-version: 24 + - id: next run: | - echo "✅ Successfully deployed logdash:${{ steps.version.outputs.version }} to GitHub Packages" - echo "📦 Available at: https://github.com/logdash-io/java-sdk/packages" + next=$(node scripts/version.ts) # on its own line, so a crash fails the step instead of releasing nothing + echo "version=$next" >> "$GITHUB_OUTPUT" - maven-central-release: - name: Release to Maven Central + # Publishes the merge to Maven Central, then tags it with a GitHub release. + release: + needs: [ validate, test, test-scripts, security-scan, version ] + if: needs.version.outputs.next != '' runs-on: ubuntu-latest - needs: [ build, security-scan ] - timeout-minutes: 30 - if: startsWith(github.ref, 'refs/tags/v') - + timeout-minutes: 45 + permissions: + contents: write # the release and its tag + env: + V: ${{ needs.version.outputs.next }} steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup JDK - uses: actions/setup-java@v4 + - uses: actions/checkout@v7 + with: + persist-credentials: false # keeps the token off disk while Maven plugins run + # A rerun after a deploy that succeeded skips to the tag, since Central rejects a version twice. + - id: central + run: | + token=$(printf '%s:%s' "$CENTRAL_TOKEN_USERNAME" "$CENTRAL_TOKEN_PASSWORD" | base64 -w0) + published=$(curl -fsS -H "Authorization: Bearer $token" "https://central.sonatype.com/api/v1/publisher/published?namespace=io.logdash&name=logdash&version=$V" | jq -r '.published') + echo "published=$published" >> "$GITHUB_OUTPUT" + env: + CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} + CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} + - if: steps.central.outputs.published != 'true' + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: 'temurin' - - - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v6 + cache: maven + server-id: central + server-username-env-var: CENTRAL_TOKEN_USERNAME + server-password-env-var: CENTRAL_TOKEN_PASSWORD + # Imports the signing key and gives gpg-agent its passphrase. + - if: steps.central.outputs.published != 'true' + uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} - - - name: Verify GPG import - run: | - gpg --list-secret-keys --keyid-format LONG - echo "✅ GPG key imported successfully" - - - name: Configure Maven Settings for Central - uses: whelk-io/maven-settings-xml-action@v22 - with: - servers: | - [ - { - "id": "central", - "username": "${env.CENTRAL_TOKEN_USERNAME}", - "password": "${env.CENTRAL_TOKEN_PASSWORD}" - } - ] - - - name: Extract Version Info - id: version - run: | - VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - - # Validate version matches tag - if [[ "v$VERSION" != "${GITHUB_REF#refs/tags/}" ]]; then - echo "::error::Version mismatch: POM version ($VERSION) doesn't match tag (${GITHUB_REF#refs/tags/})" - exit 1 - fi - - - name: Check if version exists on Maven Central - run: | - VERSION=${{ steps.version.outputs.version }} - # Check if version exists on Maven Central using search API - SEARCH_RESULT=$(curl -s "https://search.maven.org/solrsearch/select?q=g:io.logdash+AND+a:logdash+AND+v:$VERSION&rows=1&wt=json") - COUNT=$(echo "$SEARCH_RESULT" | jq -r '.response.numFound') - - if [[ "$COUNT" != "0" ]]; then - echo "::error::Version $VERSION already exists on Maven Central" - echo "Cannot redeploy existing version to Maven Central" - exit 1 - else - echo "✅ Version $VERSION is available for Maven Central release" - fi - - - name: Build and Deploy to Maven Central - run: | - mvn clean deploy -B --no-transfer-progress \ - -Pmaven-central-release \ - -DskipTests=true \ - -Dgpg.keyname="$GPG_KEY_ID" + - if: steps.central.outputs.published != 'true' + run: mvn -B --no-transfer-progress org.codehaus.mojo:versions-maven-plugin:2.23.0:set -DnewVersion="$V" -DgenerateBackupPoms=false + # Signs, uploads to the Central Portal, and waits until it is published. + - if: steps.central.outputs.published != 'true' + run: mvn clean deploy -B --no-transfer-progress -Pmaven-central-release -DskipTests -Dgpg.keyname="$GPG_KEY_ID" env: CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} - - - name: Verify Maven Central Deployment - run: | - VERSION=${{ steps.version.outputs.version }} - # Check if artifact appears in Central Portal (deployment confirmation) - if curl -s -f "https://central.sonatype.com/api/internal/browse/component?namespace=io.logdash&name=logdash&version=$VERSION" > /dev/null; then - echo "✅ Deployment confirmed via Central Portal API" - else - echo "⚠️ Not yet visible via Central Portal API (may take time to propagate)" - fi - - - name: Notify Maven Central Success - run: | - echo "✅ Successfully deployed logdash:${{ steps.version.outputs.version }} to Maven Central" - echo "📦 Will be available at: https://central.sonatype.com/artifact/io.logdash/logdash/${{ steps.version.outputs.version }}" - echo "🌐 Maven Central: https://search.maven.org/artifact/io.logdash/logdash/${{ steps.version.outputs.version }}" - echo "⏱️ Note: It may take up to 2 hours for the artifact to be searchable via Maven Central" - - create-github-release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [ github-release, maven-central-release ] - timeout-minutes: 10 - if: startsWith(github.ref, 'refs/tags/v') - - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup JDK - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: 'temurin' - - - name: Extract Version Info - id: version - run: | - VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Build Release Artifacts - run: | - mvn clean package -B --no-transfer-progress -DskipTests=true - mkdir -p release-assets - cp target/logdash-${{ steps.version.outputs.version }}.jar release-assets/ - cp target/logdash-${{ steps.version.outputs.version }}-sources.jar release-assets/ - cp target/logdash-${{ steps.version.outputs.version }}-javadoc.jar release-assets/ - - # Create checksums - cd release-assets - sha256sum *.jar > checksums.sha256 - cd .. - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - files: | - release-assets/*.jar - release-assets/checksums.sha256 - generate_release_notes: true - draft: false - body: | - # 🚀 Logdash Java SDK ${{ steps.version.outputs.version }} - - Official Java SDK for [Logdash.io](https://logdash.io) – Zero-configuration observability for developers. - - ## ✨ Features - - - **Zero Configuration**: Start logging and tracking metrics in seconds - - **Real-time Dashboard**: Cloud-hosted interface with live data updates - - **Structured Logging**: Multiple log levels with rich context support - - **Custom Metrics**: Track counters, gauges, and business metrics - - **Async & Fast**: Non-blocking, production-ready, framework-agnostic - - **Java 17+ Compatible**: Works with Java 17, 21, 22+ - - ## 📦 Installation - - **Maven Central** - ```xml - - io.logdash - logdash - ${{ steps.version.outputs.version }} - - ``` - - **Gradle** - ```gradle - implementation 'io.logdash:logdash:${{ steps.version.outputs.version }}' - ``` - - ## 🏁 Quick Start - - ```java - var logdash = Logdash.builder() - .apiKey("your-api-key") - .build(); - - logdash.logger().info("Application started"); - logdash.metrics().mutate("app_starts", 1); - ``` - - ## ⚙️ Requirements - - - Java 17 or higher - - Internet connection - - Logdash API key ([get yours](https://logdash.io)) - - ## 📖 Documentation & Support - - - [Full Documentation](https://logdash.io/docs) - - [Live Demo](https://logdash.io/demo-dashboard) - - [GitHub Issues](https://github.com/logdash-io/java-sdk/issues) - - [Discord Community](https://discord.gg/naftPW4Hxe) - - [Email Support](mailto:contact@logdash.io) - - --- - - _See below for full release notes and change log._ - - - name: Deployment Summary - run: | - echo "📋 Deployment Status Summary:" - echo "✅ GitHub Packages: ${{ needs.github-release.result }}" - echo "✅ Maven Central: ${{ needs.maven-central-release.result }}" - echo "📦 Version: ${{ steps.version.outputs.version }}" - echo "🔗 Maven Central: https://central.sonatype.com/artifact/io.logdash/logdash/${{ steps.version.outputs.version }}" - - notification: - name: Notification - runs-on: ubuntu-latest - needs: [ validate, test, build, security-scan, github-release, maven-central-release ] - if: always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) - - steps: - - name: Check Workflow Status - run: | - if [[ "${{ needs.validate.result }}" == "failure" || "${{ needs.test.result }}" == "failure" || "${{ needs.build.result }}" == "failure" ]]; then - echo "❌ Workflow failed" - echo "::error::Critical job failed" - elif [[ "${{ startsWith(github.ref, 'refs/tags/v') }}" == "true" ]]; then - if [[ "${{ needs.github-release.result }}" == "success" && "${{ needs.maven-central-release.result }}" == "success" ]]; then - echo "🎉 Release completed successfully!" - echo "📦 Available on GitHub Packages and Maven Central" - else - echo "⚠️ Release partially failed" - echo "GitHub Release: ${{ needs.github-release.result }}" - echo "Maven Central: ${{ needs.maven-central-release.result }}" - fi - else - echo "✅ Workflow completed successfully on main branch" - fi + # Last, since it creates the tag that marks the version released. + - run: gh release create "v$V" --generate-notes --target "$GITHUB_SHA" + env: + GH_TOKEN: ${{ github.token }} + + # Installs the version just released from Maven Central and checks that its logs and metrics arrive. + check-deployed-package: + needs: [ version, release ] + uses: ./.github/workflows/check-deployed-package.yml + with: + version: ${{ needs.version.outputs.next }} + secrets: + LOGDASH_API_KEY: ${{ secrets.LOGDASH_API_KEY }} + LOGDASH_PERSONAL_API_KEY: ${{ secrets.LOGDASH_PERSONAL_API_KEY }} diff --git a/.github/workflows/develop-snapshots.yml b/.github/workflows/develop-snapshots.yml deleted file mode 100644 index d80fa41..0000000 --- a/.github/workflows/develop-snapshots.yml +++ /dev/null @@ -1,283 +0,0 @@ -name: Develop Branch Snapshots - -on: - push: - branches: [ develop ] - schedule: - - cron: '0 2 * * *' - workflow_dispatch: - -env: - JAVA_VERSION: '17' - JAVA_DISTRIBUTION: 'temurin' - -jobs: - validate-develop: - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/develop' - timeout-minutes: 15 - outputs: - snapshot-version: ${{ steps.version.outputs.snapshot-version }} - - steps: - - name: Checkout develop - uses: actions/checkout@v4 - with: - ref: develop - fetch-depth: 0 - - - name: Setup JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: ${{ env.JAVA_DISTRIBUTION }} - - - name: Cache Maven dependencies - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-develop-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven-develop- - ${{ runner.os }}-maven- - - - name: Verify snapshot version - id: version - run: | - VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - if [[ ! $VERSION =~ .*-SNAPSHOT$ ]]; then - echo "❌ Error: Develop branch must have SNAPSHOT version, found: $VERSION" - exit 1 - fi - echo "✅ Snapshot version verified: $VERSION" - echo "snapshot-version=$VERSION" >> $GITHUB_OUTPUT - - - name: Run quick validation - run: | - mvn clean validate compile -B --no-transfer-progress \ - -DskipTests=true \ - -Dspotless.check.skip=true - - maven-central-snapshot: - runs-on: ubuntu-latest - needs: validate-develop - timeout-minutes: 25 - if: github.ref == 'refs/heads/develop' - - steps: - - name: Checkout develop - uses: actions/checkout@v4 - with: - ref: develop - fetch-depth: 0 - - - name: Setup JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: ${{ env.JAVA_DISTRIBUTION }} - - - name: Cache Maven dependencies - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-develop-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven-develop- - ${{ runner.os }}-maven- - - - name: Configure Maven Central Snapshots - uses: whelk-io/maven-settings-xml-action@v22 - with: - servers: | - [ - { - "id": "central", - "username": "${{ secrets.CENTRAL_TOKEN_USERNAME }}", - "password": "${{ secrets.CENTRAL_TOKEN_PASSWORD }}" - } - ] - - - name: Display snapshot version - run: | - echo "📦 Deploying snapshot version: ${{ needs.validate-develop.outputs.snapshot-version }}" - - - name: Run comprehensive tests - run: | - mvn clean verify -B --no-transfer-progress \ - -Dmaven.test.failure.ignore=false \ - -Dfailsafe.rerunFailingTestsCount=2 - - - name: Deploy to Maven Central Snapshots - run: | - mvn deploy -B --no-transfer-progress \ - -Pmaven-central-release \ - -DskipTests=true \ - -Dcentral.autoPublish=true - env: - SONATYPE_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} - SONATYPE_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} - - - name: Verify Maven Central snapshot deployment - run: | - VERSION="${{ needs.validate-develop.outputs.snapshot-version }}" - echo "⏳ Waiting for snapshot propagation..." - sleep 10 - - # Check if snapshot metadata is available - METADATA_URL="https://central.sonatype.com/repository/maven-snapshots/io/logdash/logdash/$VERSION/maven-metadata.xml" - if curl -s -f "$METADATA_URL" > /dev/null; then - echo "✅ Snapshot metadata confirmed at Maven Central" - else - echo "⚠️ Snapshot metadata not yet available (may take time to propagate)" - fi - - - name: Display deployment info - run: | - VERSION="${{ needs.validate-develop.outputs.snapshot-version }}" - echo "✅ Successfully deployed $VERSION to Maven Central Snapshots" - echo "📦 Available at: https://central.sonatype.com/repository/maven-snapshots/io/logdash/logdash/$VERSION/" - echo "" - echo "🔍 Maven dependency:" - echo "" - echo " io.logdash" - echo " logdash" - echo " $VERSION" - echo "" - echo "" - echo "🔍 Gradle dependency:" - echo "implementation 'io.logdash:logdash:$VERSION'" - - - name: Notify deployment status - if: failure() - run: | - VERSION="${{ needs.validate-develop.outputs.snapshot-version }}" - echo "❌ Snapshot deployment failed for version $VERSION" - echo "Check logs above for deployment errors" - - github-packages-snapshot: - runs-on: ubuntu-latest - needs: validate-develop - timeout-minutes: 15 - if: github.ref == 'refs/heads/develop' - - permissions: - contents: read - packages: write - - steps: - - name: Checkout develop - uses: actions/checkout@v4 - with: - ref: develop - - - name: Setup JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: ${{ env.JAVA_DISTRIBUTION }} - - - name: Cache Maven dependencies - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-develop-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven-develop- - ${{ runner.os }}-maven- - - - name: Configure Maven Settings for GitHub Packages - uses: whelk-io/maven-settings-xml-action@v22 - with: - servers: | - [ - { - "id": "github", - "username": "${env.GITHUB_ACTOR}", - "password": "${env.GITHUB_TOKEN}" - } - ] - - - name: Deploy to GitHub Packages (Backup) - run: | - mvn deploy -B --no-transfer-progress \ - -Pgithub-release \ - -DperformRelease=true \ - -DskipTests=true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Verify GitHub Packages deployment - run: | - VERSION="${{ needs.validate-develop.outputs.snapshot-version }}" - echo "✅ Backup snapshot deployed to GitHub Packages: $VERSION" - echo "📦 Available at: https://github.com/logdash-io/java-sdk/packages" - - verify-deployments: - runs-on: ubuntu-latest - needs: [ maven-central-snapshot, github-packages-snapshot ] - timeout-minutes: 10 - if: github.ref == 'refs/heads/develop' - - steps: - - name: Final verification - run: | - VERSION="${{ needs.validate-develop.outputs.snapshot-version }}" - echo "🔍 Final verification for snapshot: $VERSION" - - # Wait a bit more for propagation - echo "⏳ Waiting for complete propagation..." - sleep 10 - - # Check Maven Central snapshots - METADATA_URL="https://central.sonatype.com/repository/maven-snapshots/io/logdash/logdash/$VERSION/maven-metadata.xml" - if curl -s -f "$METADATA_URL" > /dev/null; then - echo "✅ Maven Central snapshot confirmed and accessible" - else - echo "⚠️ Maven Central snapshot metadata not accessible yet" - fi - - echo "" - echo "📋 Deployment Summary:" - echo "🏗️ Maven Central Snapshots: ${{ needs.maven-central-snapshot.result }}" - echo "📦 GitHub Packages: ${{ needs.github-packages-snapshot.result }}" - echo "🔢 Version: $VERSION" - echo "🌐 Repository: https://central.sonatype.com/repository/maven-snapshots/" - - notification: - runs-on: ubuntu-latest - needs: [ validate-develop, maven-central-snapshot, github-packages-snapshot, verify-deployments ] - if: always() && github.ref == 'refs/heads/develop' - - steps: - - name: Workflow status summary - run: | - VERSION="${{ needs.validate-develop.outputs.snapshot-version }}" - - echo "📊 Develop Branch Snapshot Workflow Summary" - echo "=============================================" - echo "🔢 Version: $VERSION" - echo "📅 Triggered: ${{ github.event_name }}" - echo "" - echo "Job Results:" - echo "✓ Validation: ${{ needs.validate-develop.result }}" - echo "✓ Maven Central: ${{ needs.maven-central-snapshot.result }}" - echo "✓ GitHub Packages: ${{ needs.github-packages-snapshot.result }}" - echo "✓ Verification: ${{ needs.verify-deployments.result }}" - echo "" - - # Check overall success - if [[ "${{ needs.validate-develop.result }}" == "success" && - "${{ needs.maven-central-snapshot.result }}" == "success" && - "${{ needs.github-packages-snapshot.result }}" == "success" ]]; then - echo "🎉 All snapshot deployments completed successfully!" - echo "📦 $VERSION is now available for testing" - else - echo "⚠️ Some deployments may have failed. Check individual job results above." - fi - - echo "" - echo "🔗 Quick Access:" - echo "- Maven Central: https://central.sonatype.com/repository/maven-snapshots/io/logdash/logdash/$VERSION/" - echo "- GitHub Packages: https://github.com/logdash-io/java-sdk/packages" - echo "- Workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ No newline at end of file diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..74fcf0b --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,20 @@ +name: PR title + +# Squash merges use the PR title as the commit subject, and scripts/version.ts reads it to pick the next version. +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +permissions: {} + +jobs: + conventional: + runs-on: ubuntu-latest + steps: + - env: + TITLE: ${{ github.event.pull_request.title }} + run: | + printf '%s' "$TITLE" | grep -qE '^(feat|fix|perf|refactor|docs|test|build|ci|chore|style|revert)(\(.+\))?!?: .+' || { + echo "::error::PR title \"$TITLE\" is not a Conventional Commit, e.g. \"feat(metrics): add histograms\" or \"fix(transport): retry after a 429\". feat releases a minor, fix/perf a patch, ! a major; other types do not release." + exit 1 + } diff --git a/README.md b/README.md index fb651b1..d51173c 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Then use in your project: io.logdash logdash - 0.2.0 + 0.0.0-SNAPSHOT ``` diff --git a/check-deployed-package/Check.java b/check-deployed-package/Check.java index 48e50c3..f642e3f 100644 --- a/check-deployed-package/Check.java +++ b/check-deployed-package/Check.java @@ -2,28 +2,28 @@ import io.logdash.sdk.Logdash; - +/** + * Sends a log of each level and a metric with the SDK as published, for run.sh to read back. + */ public class Check { public static void main(String[] args) { - System.out.println("=== LogDash Java SDK Demo ==="); - - // Get package version (would need to be handled differently in a real scenario) - System.out.println("Using logdash package version: " + getLogdashVersion()); - System.out.println(); - - String apiKey = System.getenv("LOGDASH_API_KEY"); String logsSeed = System.getenv().getOrDefault("LOGS_SEED", "default"); - String metricsSeed = System.getenv().getOrDefault("METRICS_SEED", "1"); - - System.out.println("Using API Key: " + apiKey); - System.out.println("Using Logs Seed: " + logsSeed); - System.out.println("Using Metrics Seed: " + metricsSeed); - - try (Logdash logdash = Logdash.create(apiKey)) { + int metricsSeed = Integer.parseInt(System.getenv().getOrDefault("METRICS_SEED", "1")); + + System.out.println("=== Logdash Java SDK check ==="); + System.out.println("Using logdash " + System.getenv("LOGDASH_VERSION")); + System.out.println("Using logs seed: " + logsSeed); + System.out.println("Using metrics seed: " + metricsSeed); + + try (Logdash logdash = + Logdash.builder() + .apiKey(System.getenv("LOGDASH_API_KEY")) + .baseUrl(System.getenv().getOrDefault("LOGDASH_API_URL", "https://api.logdash.io")) + .app("java-sdk") + .build()) { var logger = logdash.logger(); var metrics = logdash.metrics(); - // Log some messages with seed appended logger.info("This is an info log " + logsSeed); logger.error("This is an error log " + logsSeed); logger.warn("This is a warning log " + logsSeed); @@ -33,25 +33,10 @@ public static void main(String[] args) { logger.info("This is an info log " + logsSeed); logger.verbose("This is a verbose log " + logsSeed); - // Set and mutate metrics with seed - int seedValue = Integer.parseInt(metricsSeed); - metrics.set("users", seedValue); - metrics.mutate("users", 1); - - // Wait to ensure data is sent - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - - private static String getLogdashVersion() { - try { - return Check.class.getPackage().getImplementationVersion(); - } catch (Exception e) { - return "unknown"; - } + metrics.set("users_java", metricsSeed); + // The mutate adds to the set, so the set must arrive first + logdash.flush(); + metrics.mutate("users_java", 1); + } // close sends what is still queued } } diff --git a/check-deployed-package/Dockerfile b/check-deployed-package/Dockerfile index 32537e5..27987e7 100644 --- a/check-deployed-package/Dockerfile +++ b/check-deployed-package/Dockerfile @@ -1,25 +1,14 @@ -FROM eclipse-temurin:17-jdk +FROM maven:3.9-eclipse-temurin-17 WORKDIR /app -# Install Maven -RUN apt-get update && apt-get install -y maven && rm -rf /var/lib/apt/lists/* +# The SDK version to install from Maven Central, into this image's empty local repository +ARG LOGDASH_VERSION +ENV LOGDASH_VERSION=${LOGDASH_VERSION} -# Copy project files COPY check-deployed-package/pom.xml . -COPY check-deployed-package/Check.java src/main/java/com/logdash/check/Check.java +COPY check-deployed-package/Check.java src/main/java/io/logdash/check/Check.java -# Download dependencies and compile -RUN mvn dependency:resolve compile -B --no-transfer-progress +RUN mvn compile -B --no-transfer-progress -Dlogdash.version="$LOGDASH_VERSION" -# Add environment variable validation -ENV LOGDASH_API_KEY="" -ENV LOGS_SEED="" -ENV METRICS_SEED="" - -# Run the application with environment validation -CMD if [ -z "$LOGDASH_API_KEY" ]; then \ - echo "Error: LOGDASH_API_KEY environment variable is required"; \ - exit 1; \ - fi; \ - mvn exec:java -B --no-transfer-progress +CMD ["sh", "-c", "exec mvn exec:java -B --no-transfer-progress -q -Dlogdash.version=\"$LOGDASH_VERSION\""] diff --git a/check-deployed-package/README.md b/check-deployed-package/README.md index 4941f66..76698b3 100644 --- a/check-deployed-package/README.md +++ b/check-deployed-package/README.md @@ -1,8 +1,14 @@ -# LogDash Java SDK Demo +# Logdash Java SDK check -Use command below to test if the published SDK works: +Checks the SDK as Maven Central serves it: installs it in a clean container, sends logs and metrics with it, and reads them back. +CI runs it after each release. ``` -LOGDASH_API_KEY= ./check-deployed-package/run.sh +LOGDASH_API_KEY= LOGDASH_PERSONAL_API_KEY= ./check-deployed-package/run.sh ``` +`LOGDASH_API_KEY` is the ingest key of the "SDK smoke tests" domain, from its Settings page. +The check sends as the app `java-sdk`, so each SDK shows up as its own app on that domain. +`LOGDASH_PERSONAL_API_KEY` is an `ldp_` personal API key with `logs:read` and `metrics:read`, restricted to that domain. +`LOGDASH_VERSION` picks the version to check, the latest release by default. +`LOGDASH_API_URL` picks the API, `https://api.logdash.io` by default. diff --git a/check-deployed-package/pom.xml b/check-deployed-package/pom.xml index 9bed4ed..aa6335b 100644 --- a/check-deployed-package/pom.xml +++ b/check-deployed-package/pom.xml @@ -4,7 +4,7 @@ io.logdash check - 0.2.0 + 0.0.0-SNAPSHOT 17 @@ -18,24 +18,10 @@ io.logdash logdash - 0.2.0 + ${logdash.version} - - - - true - - - true - - github - GitHub Packages - https://maven.pkg.github.com/logdash-io/java-sdk - - - diff --git a/check-deployed-package/run.sh b/check-deployed-package/run.sh index 9bb06d7..92cd5fc 100755 --- a/check-deployed-package/run.sh +++ b/check-deployed-package/run.sh @@ -1,126 +1,97 @@ #!/bin/sh -set -e - -# Generate random 5-character seed -LOGS_SEED=$(openssl rand -hex 2 | cut -c1-5) -echo "Generated seed: $LOGS_SEED" - -# Generate random metrics seed (1-1,000,000) -METRICS_SEED=$(awk 'BEGIN{srand(); print int(rand()*1000000)+1}') -echo "Generated metrics seed: $METRICS_SEED" - -echo "Building LogDash Java demo Docker image (using published package)..." -docker build --no-cache \ - -t logdash-java-demo \ - -f check-deployed-package/Dockerfile . - -echo -echo "Running LogDash Java demo..." -echo - -# Run in non-interactive mode which works everywhere -docker run --rm \ - -e LOGDASH_API_KEY="${LOGDASH_API_KEY}" \ - -e LOGS_SEED="${LOGS_SEED}" \ - -e METRICS_SEED="${METRICS_SEED}" \ - logdash-java-demo - -echo -echo "Demo completed!" - -echo -echo "Authenticating with LogDash API..." - -# Authenticate with API using the API key -AUTH_RESPONSE=$(curl -s -X 'POST' \ - 'https://api.logdash.io/auth/api-key' \ - -H 'accept: application/json' \ - -H 'Content-Type: application/json' \ - -d "{ - \"apiKey\": \"${LOGDASH_API_KEY}\" -}") - -# Extract token and projectId from response -TOKEN=$(echo "$AUTH_RESPONSE" | grep -o '"token":"[^"]*"' | sed 's/"token":"\(.*\)"/\1/') -PROJECT_ID=$(echo "$AUTH_RESPONSE" | grep -o '"projectId":"[^"]*"' | sed 's/"projectId":"\(.*\)"/\1/') - -if [ -z "$TOKEN" ] || [ -z "$PROJECT_ID" ]; then - echo "Error: Failed to authenticate with LogDash API" - echo "Response: $AUTH_RESPONSE" +# Checks the SDK as Maven Central serves it: installs it in a clean container, sends logs and metrics +# with it, and reads them back. +# LOGDASH_API_KEY: the ingest key of the "SDK smoke tests" domain, which sends as the app java-sdk. +# LOGDASH_PERSONAL_API_KEY: an ldp_ personal API key with logs:read and metrics:read, restricted to +# that one domain, which reads back. +# LOGDASH_VERSION: the version to check; empty checks the latest release. +# LOGDASH_API_URL: the API both halves talk to, https://api.logdash.io by default. +set -eu + +: "${LOGDASH_API_KEY:?must be the ingest key of the smoke-test domain}" +: "${LOGDASH_PERSONAL_API_KEY:?must be a personal API key that reads the smoke-test domain}" +API=${LOGDASH_API_URL:-https://api.logdash.io} +CENTRAL=https://repo.maven.apache.org/maven2/io/logdash/logdash +VERSION=${LOGDASH_VERSION:-$(curl -fsS "$CENTRAL/maven-metadata.xml" | sed -n 's:.*\(.*\).*:\1:p')} +VERSION=${VERSION#v} +if [ -z "$VERSION" ]; then + echo "Error: no version to check, and Maven Central names no latest release" exit 1 fi -echo "Authentication successful. Project ID: $PROJECT_ID" - -echo -echo "Fetching logs from LogDash API..." - -# Fetch logs from the API -LOGS_RESPONSE=$(curl -s -X 'GET' \ - "https://api.logdash.io/projects/${PROJECT_ID}/logs?limit=10" \ - -H 'accept: application/json' \ - -H "Authorization: Bearer ${TOKEN}") - -echo "Logs fetched successfully" - -echo -echo "Validating log messages..." - -# Expected log messages with seed -EXPECTED_MESSAGES="This is an info log ${LOGS_SEED} -This is an error log ${LOGS_SEED} -This is a warning log ${LOGS_SEED} -This is a debug log ${LOGS_SEED} -This is a http log ${LOGS_SEED} -This is a silly log ${LOGS_SEED} -This is an info log ${LOGS_SEED} -This is a verbose log ${LOGS_SEED}" - -# Check if all expected messages are present in the logs -echo "$EXPECTED_MESSAGES" | while IFS= read -r expected_msg; do - if ! echo "$LOGS_RESPONSE" | grep -q "$expected_msg"; then - echo "Error: Expected log message not found: '$expected_msg'" - echo "Logs response: $LOGS_RESPONSE" +# A release reaches Maven Central some minutes after the Central Portal publishes it. +echo "Waiting until Maven Central serves logdash $VERSION..." +tries=0 +until curl -fsIo /dev/null "$CENTRAL/$VERSION/logdash-$VERSION.pom" && curl -fsIo /dev/null "$CENTRAL/$VERSION/logdash-$VERSION.jar"; do + tries=$((tries + 1)) + if [ "$tries" -ge 120 ]; then + echo "Error: Maven Central does not serve logdash $VERSION after an hour" exit 1 fi - echo "✓ Found: '$expected_msg'" + sleep 30 done -echo -echo "Fetching metrics from LogDash API..." - -# Fetch metrics from the API -METRICS_RESPONSE=$(curl -s -X 'GET' \ - "https://api.logdash.io/projects/${PROJECT_ID}/metrics" \ - -H 'accept: application/json' \ - -H "Authorization: Bearer ${TOKEN}") +LOGS_SEED=$(openssl rand -hex 4) +METRICS_SEED=$(awk 'BEGIN { srand(); print int(rand() * 1000000) + 1 }') +echo "Logs seed: $LOGS_SEED, metrics seed: $METRICS_SEED" -echo "Metrics fetched successfully" +docker build --no-cache --build-arg LOGDASH_VERSION="$VERSION" -t logdash-java-check -f check-deployed-package/Dockerfile . +docker run --rm -e LOGDASH_API_KEY -e LOGDASH_API_URL="$API" -e LOGS_SEED="$LOGS_SEED" -e METRICS_SEED="$METRICS_SEED" logdash-java-check -echo -echo "Validating metrics..." +# GETs an API path with the personal key: prints the response, and fails on an HTTP error. +get() { + curl -sS --fail-with-body -H "Authorization: Bearer $LOGDASH_PERSONAL_API_KEY" "$API$1" +} -# Expected users metric value (metrics_seed + 1) -EXPECTED_USERS_VALUE=$((METRICS_SEED + 1)) - -# Check if users metric exists with correct value -if ! echo "$METRICS_RESPONSE" | grep -q '"name":"users"'; then - echo "Error: Users metric not found" - echo "Metrics response: $METRICS_RESPONSE" +WHOAMI=$(get /personal-api-keys/whoami) || { + echo "Error: whoami rejects LOGDASH_PERSONAL_API_KEY: $WHOAMI" exit 1 -fi - -# Extract the value of the users metric and check if it matches expected value -USERS_VALUE=$(echo "$METRICS_RESPONSE" | sed 's/},{/}\n{/g' | grep '"name":"users"' | grep -o '"value":[0-9]*' | sed 's/"value"://') - -if [ "$USERS_VALUE" != "$EXPECTED_USERS_VALUE" ]; then - echo "Error: Users metric value mismatch. Expected: $EXPECTED_USERS_VALUE, Found: $USERS_VALUE" - echo "Metrics response: $METRICS_RESPONSE" +} +DOMAIN_ID=$(printf '%s' "$WHOAMI" | jq -r 'if .access.kind == "clusters" and (.access.ids | length) == 1 then .access.ids[0] else empty end') +if [ -z "$DOMAIN_ID" ]; then + echo "Error: LOGDASH_PERSONAL_API_KEY must be restricted to the one smoke-test domain: $WHOAMI" exit 1 fi - -echo "✓ Found users metric with correct value: $USERS_VALUE" - -echo -echo "All expected log messages and metrics found successfully!" -echo "Validation completed!" \ No newline at end of file +echo "Reading back from domain $DOMAIN_ID" + +EXPECTED_LOGS=$(jq -n --arg seed "$LOGS_SEED" '[ + ["info", "This is an info log \($seed)"], + ["error", "This is an error log \($seed)"], + ["warning", "This is a warning log \($seed)"], + ["debug", "This is a debug log \($seed)"], + ["http", "This is a http log \($seed)"], + ["silly", "This is a silly log \($seed)"], + ["verbose", "This is a verbose log \($seed)"] +]') +EXPECTED_USERS=$((METRICS_SEED + 1)) + +# Ingestion is asynchronous, so each read-back retries for a minute. +LOGS='' MISSING='' tries=0 +until + LOGS=$(get "/clusters/$DOMAIN_ID/logs?apps=java-sdk&limit=100&searchString=$LOGS_SEED") && + MISSING=$(printf '%s' "$LOGS" | jq -r --argjson want "$EXPECTED_LOGS" '$want - [.[] | [.level, .message]] | .[] | join(": ")') && + [ -z "$MISSING" ] +do + tries=$((tries + 1)) + if [ "$tries" -ge 12 ]; then + printf 'Error: logs not found:\n%s\nResponse: %s\n' "$MISSING" "$LOGS" + exit 1 + fi + sleep 5 +done +echo "✓ Found a log of each of the 7 levels" + +METRICS='' USERS='' tries=0 +until + METRICS=$(get "/clusters/$DOMAIN_ID/metrics") && + USERS=$(printf '%s' "$METRICS" | jq -r '.[] | select(.app == "java-sdk" and .name == "users_java") | .value') && + [ "$USERS" = "$EXPECTED_USERS" ] +do + tries=$((tries + 1)) + if [ "$tries" -ge 12 ]; then + echo "Error: metric users_java is ${USERS:-missing}, expected $EXPECTED_USERS. Response: $METRICS" + exit 1 + fi + sleep 5 +done +echo "✓ Found metric users_java = $USERS" diff --git a/examples/example-simple-java/pom.xml b/examples/example-simple-java/pom.xml index ff4b4f7..4908fb2 100644 --- a/examples/example-simple-java/pom.xml +++ b/examples/example-simple-java/pom.xml @@ -58,25 +58,4 @@ - - - - snapshot-repo - - - logdash.version - *-SNAPSHOT - - - - - - true - - github-snapshots - https://maven.pkg.github.com/logdash-io/java-sdk - - - - diff --git a/pom.xml b/pom.xml index dbc609e..7e1a9a9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,8 @@ io.logdash logdash - 0.2.0 + + 0.0.0-SNAPSHOT jar Logdash Java SDK @@ -48,13 +49,14 @@ 3.27.3 3.13.0 2.0.17 - 2.19.0 + 2.22.4 3.14.0 3.5.3 3.5.3 0.8.13 + 3.5.1 3.3.1 3.11.2 4.9.3.0 @@ -66,6 +68,19 @@ 0.7.0 + + + + + com.fasterxml.jackson + jackson-bom + ${jackson.version} + pom + import + + + + @@ -76,22 +91,18 @@ com.fasterxml.jackson.core jackson-core - ${jackson.version} com.fasterxml.jackson.core jackson-databind - ${jackson.version} com.fasterxml.jackson.core jackson-annotations - ${jackson.version} com.fasterxml.jackson.datatype jackson-datatype-jsr310 - ${jackson.version} @@ -365,6 +376,20 @@ + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + true + + + + + org.apache.maven.plugins @@ -502,43 +527,6 @@ - - - github-release - - - performRelease - true - - - - - github - GitHub Packages - https://maven.pkg.github.com/logdash-io/java-sdk - - - github - GitHub Packages - https://maven.pkg.github.com/logdash-io/java-sdk - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - ${maven-deploy-plugin.version} - - true - false - - - - - - maven-central-release diff --git a/scripts/dev-cycle.sh b/scripts/dev-cycle.sh index 4322e7f..1131043 100755 --- a/scripts/dev-cycle.sh +++ b/scripts/dev-cycle.sh @@ -6,6 +6,6 @@ mvn clean install -DskipTests echo "🚀 Testing SpringBoot example with local snapshot..." cd examples/example-springboot -mvn clean spring-boot:run -Plocal-dev +mvn clean spring-boot:run -Plocal-dev -Dlogdash-sdk.version=0.0.0-SNAPSHOT echo "✅ Development cycle completed!" diff --git a/scripts/version.test.ts b/scripts/version.test.ts new file mode 100644 index 0000000..316cade --- /dev/null +++ b/scripts/version.test.ts @@ -0,0 +1,16 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { nextVersion } from './version.ts' + +test('the next version follows the Conventional Commits since the last release', () => { + assert.equal(nextVersion(null, []), '0.1.0', 'first release') + assert.equal(nextVersion('0.1.0', []), null, 'nothing merged') + assert.equal(nextVersion('0.1.0', ['chore: deps', 'ci(release): cache', 'docs: readme']), null, 'nothing users get') + assert.equal(nextVersion('0.1.3', ['fix: crash on #', 'chore: x']), '0.1.4') + assert.equal(nextVersion('0.1.3', ['perf(export): faster']), '0.1.4') + assert.equal(nextVersion('0.1.3', ['fix: a', 'feat(timeline): b']), '0.2.0') + assert.equal(nextVersion('0.9.3', ['feat!: new format']), '1.0.0') + assert.equal(nextVersion('0.9.3', ['refactor(projects): x\n\nBREAKING CHANGE: old bundles no longer open']), '1.0.0') + assert.equal(nextVersion('1.2.3', ['Feature: no prefix', 'fixed: typo', 'feat : spaced', 'featx: y']), null, 'only exact types count') + assert.equal(nextVersion('1.2.3', ['chore: x\n\n* feat: from a squashed commit list']), null, 'only subjects count') +}) diff --git a/scripts/version.ts b/scripts/version.ts new file mode 100644 index 0000000..7c9989f --- /dev/null +++ b/scripts/version.ts @@ -0,0 +1,26 @@ +// `node scripts/version.ts`: the version the commits since the last release call for, from their +// Conventional Commit subjects, or nothing when none of them changes the app. CI runs it after each +// merge to main and releases what it prints. Releases are tagged v; the tags are the +// source of truth, pom.xml's version is a placeholder CI overwrites before packaging. +import { execFileSync } from 'node:child_process' + +/** The next version after `last` (null before the first release), given the commit messages since it. */ +export function nextVersion(last: string | null, messages: string[]): string | null { + if (!last) return '0.1.0' + const subjects = messages.map((m) => m.split('\n')[0]) + const [major, minor, patch] = last.split('.').map(Number) + if (subjects.some((s) => /^\w+(\(.+\))?!:/.test(s)) || messages.some((m) => /^BREAKING[ -]CHANGE:/m.test(m))) return `${major + 1}.0.0` + if (subjects.some((s) => /^feat(\(.+\))?:/.test(s))) return `${major}.${minor + 1}.0` + if (subjects.some((s) => /^(fix|perf)(\(.+\))?:/.test(s))) return `${major}.${minor}.${patch + 1}` + return null +} + +if (import.meta.main) { + const git = (...args: string[]) => execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) + let tag: string | null = null + try { + tag = git('describe', '--tags', '--abbrev=0', '--match', 'v[0-9]*').trim() + } catch {} // no release yet + const messages = tag ? git('log', '--format=%B%x00', `${tag}..HEAD`).split('\0').map((m) => m.trim()).filter(Boolean) : [] + console.log(nextVersion(tag?.slice(1) ?? null, messages) ?? '') +} diff --git a/src/main/java/io/logdash/sdk/transport/HttpTransport.java b/src/main/java/io/logdash/sdk/transport/HttpTransport.java index ed5d0e7..8d4cf30 100644 --- a/src/main/java/io/logdash/sdk/transport/HttpTransport.java +++ b/src/main/java/io/logdash/sdk/transport/HttpTransport.java @@ -14,6 +14,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.*; @@ -42,7 +43,11 @@ public final class HttpTransport implements LogdashTransport, AutoCloseable { private static final String EVENTS_ENDPOINT = "/events"; private static final String API_KEY_HEADER = "project-api-key"; - private static final String USER_AGENT = "logdash-java-sdk/0.2.0"; + // The jar's Implementation-Version, the version CI releases; null when not run from the jar + private static final String USER_AGENT = + "logdash-java-sdk/" + + Objects.requireNonNullElse( + HttpTransport.class.getPackage().getImplementationVersion(), "unknown"); private static final String CONTENT_TYPE = "application/json"; private final LogdashConfig config; diff --git a/src/main/java/io/logdash/sdk/util/JsonSerializer.java b/src/main/java/io/logdash/sdk/util/JsonSerializer.java index ec2de4e..01fbefc 100644 --- a/src/main/java/io/logdash/sdk/util/JsonSerializer.java +++ b/src/main/java/io/logdash/sdk/util/JsonSerializer.java @@ -52,7 +52,8 @@ private static ObjectMapper createObjectMapper() { .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) - .serializationInclusion(JsonInclude.Include.NON_NULL) + .defaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) .build(); } diff --git a/src/test/java/io/logdash/sdk/transport/HttpTransportTest.java b/src/test/java/io/logdash/sdk/transport/HttpTransportTest.java index 1917b2e..2328b87 100644 --- a/src/test/java/io/logdash/sdk/transport/HttpTransportTest.java +++ b/src/test/java/io/logdash/sdk/transport/HttpTransportTest.java @@ -84,7 +84,7 @@ void should_successfully_send_log_entry() { postRequestedFor(urlEqualTo("/logs")) .withHeader("Content-Type", equalTo("application/json")) .withHeader("project-api-key", equalTo("test-api-key")) - .withHeader("User-Agent", equalTo("logdash-java-sdk/0.2.0"))); + .withHeader("User-Agent", matching("logdash-java-sdk/.+"))); } @Test @@ -107,7 +107,7 @@ void should_successfully_send_metric_entry() { putRequestedFor(urlEqualTo("/metrics")) .withHeader("Content-Type", equalTo("application/json")) .withHeader("project-api-key", equalTo("test-api-key")) - .withHeader("User-Agent", equalTo("logdash-java-sdk/0.2.0"))); + .withHeader("User-Agent", matching("logdash-java-sdk/.+"))); } @ParameterizedTest