Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/approval.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Approval

# Logdash team members merge their own pull requests on green checks.
# Pull requests from anyone else need a team member to approve their latest commit; outside collaborators cannot approve each other's.
# GitHub judges a required check by its newest run, so every run reads the reviews as they stand and never skips.
# ponytail: anyone with write access can edit this file on their branch, so it stops accidental merges, not deliberate ones.
on:
pull_request:
pull_request_review:
types: [submitted, dismissed]

permissions:
pull-requests: read

jobs:
approval:
name: team approval
runs-on: ubuntu-latest
steps:
- env:
GH_TOKEN: ${{ github.token }}
AUTHOR: ${{ github.event.pull_request.author_association }}
PR: ${{ github.event.pull_request.number }}
HEAD: ${{ github.event.pull_request.head.sha }}
run: |
case "$AUTHOR" in OWNER|MEMBER) echo "Opened by a Logdash team member."; exit 0 ;; esac
# Private organization members can show up as CONTRIBUTOR or NONE here, so a team review is a user review with write access from anyone but an outside collaborator.
# shellcheck disable=SC2016 # $owner, $name and $pr are GraphQL variables, not shell ones.
verdict=$(gh api graphql -F pr="$PR" -f owner="${GITHUB_REPOSITORY%/*}" -f name="${GITHUB_REPOSITORY#*/}" -f query='
query($owner: String!, $name: String!, $pr: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $pr) {
latestOpinionatedReviews(first: 100, writersOnly: true) {
nodes { state authorAssociation author { __typename } commit { oid } }
}
}
}
}' | jq -r --arg head "$HEAD" '
[.data.repository.pullRequest.latestOpinionatedReviews.nodes[] | select(.author.__typename == "User" and .authorAssociation != "COLLABORATOR")]
| if any(.state == "CHANGES_REQUESTED") then "A Logdash team member requested changes"
elif any(.state == "APPROVED" and .commit.oid == $head) then "approved"
else "No Logdash team member has approved the latest commit yet" end')
[ "$verdict" = approved ] && { echo "Approved by a Logdash team member."; exit 0; }
echo "::error::$verdict. Outside contributions merge once a Logdash team member approves their latest commit."
exit 1
37 changes: 24 additions & 13 deletions .github/workflows/check-deployed-package.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
name: Check deployed package

# The gem installs from RubyGems and what it sends arrives. CI runs it after each release against the version it
# published; run it by hand on any branch for the latest or any other version.
on:
push:
branches:
- main
- add-demo-section
workflow_call:
inputs:
version:
type: string
required: true
secrets:
LOGDASH_API_KEY:
required: true
LOGDASH_PERSONAL_API_KEY:
required: true
workflow_dispatch:
inputs:
version:
description: Version to check, the latest when empty
type: string

permissions:
contents: read

jobs:
run-demo:
name: Run LogDash Demo
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Make run script executable
run: chmod +x demo/run.sh

- name: Run LogDash demo
- uses: actions/checkout@v7
- run: ./demo/run.sh
env:
LOGDASH_VERSION: ${{ inputs.version }}
LOGDASH_API_KEY: ${{ secrets.LOGDASH_API_KEY }}
run: ./demo/run.sh
LOGDASH_PERSONAL_API_KEY: ${{ secrets.LOGDASH_PERSONAL_API_KEY }}
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI

on:
push:
branches: [main]
pull_request:

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: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- run: bundle exec rspec
- run: bundle exec rubocop
- run: gem build logdash.gemspec
- uses: actions/setup-node@v7
with:
node-version: 24
- run: node --test 'scripts/*.test.ts'

# 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
outputs:
next: ${{ steps.next.outputs.version }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # the tags and commits since the last release
- uses: actions/setup-node@v7
with:
node-version: 24
# The assignment fails the step when the script fails; inside echo the failure would vanish into an empty version.
- id: next
run: |
version=$(node scripts/version.ts)
echo "version=$version" >> "$GITHUB_OUTPUT"

# Publishes the merge to RubyGems as its trusted publisher, with a short-lived OIDC token instead of an API key.
release:
needs: [test, version]
if: needs.version.outputs.next != ''
runs-on: ubuntu-latest
permissions:
contents: write # the GitHub release and its tag
id-token: write # the OIDC token RubyGems exchanges for a push key
env:
V: ${{ needs.version.outputs.next }}
steps:
- uses: actions/checkout@v7
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
# gem push fails if this wrote nothing: the gem then builds as another version than the file it pushes.
- run: sed -i "s/VERSION = '.*'/VERSION = '$V'/" lib/logdash/version.rb
- run: gem build logdash.gemspec
- uses: rubygems/configure-rubygems-credentials@v2.1.0
# Skipped when the version is already live, so a rerun after a failed release step still tags it.
- run: curl -fs "https://rubygems.org/api/v2/rubygems/logdash/versions/$V.json" >/dev/null || gem push "logdash-$V.gem"
# Last, since it creates the v<version> tag version.ts counts from.
- run: gh release create "v$V" --generate-notes --target "$GITHUB_SHA"
env:
GH_TOKEN: ${{ github.token }}

# The gem just published installs from RubyGems, and what it sends arrives.
smoke:
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 }}
20 changes: 20 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: PR title
Comment thread
szymeo marked this conversation as resolved.

# 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(logger): send logs in order\". feat releases a minor, fix/perf a patch, ! a major; other types do not release."
exit 1
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@

# rspec failure tracking
.rspec_status

# gem build output
/*.gem
7 changes: 0 additions & 7 deletions .travis.yml

This file was deleted.

2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
logdash (1.0.2)
logdash (0.0.0)
json (~> 2.0)

GEM
Expand Down
2 changes: 1 addition & 1 deletion bin/console
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env ruby

require "bundler/setup"
require "ruby/sdk"
require "logdash"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
Expand Down
8 changes: 5 additions & 3 deletions demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
FROM ruby:3.2-alpine
FROM ruby:3.4-alpine

WORKDIR /app

RUN gem install logdash
# The version run.sh checks; ">= 0", the latest, when it is empty
ARG LOGDASH_VERSION
RUN gem install logdash --version "${LOGDASH_VERSION:->= 0}"

COPY demo/demo_script.rb /app/

RUN chmod +x /app/demo_script.rb

CMD ["./demo_script.rb"]
CMD ["./demo_script.rb"]
10 changes: 8 additions & 2 deletions demo/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# LogDash Ruby SDK Demo

Use command below to test if the published SDK works
Checks that the published gem installs from RubyGems and that what it sends arrives.
CI runs it after each release, and the `Check deployed package` workflow runs it by hand on any branch.

```
LOGDASH_API_KEY=<your-api-key> ./demo/run.sh
LOGDASH_API_KEY=<ingest key> LOGDASH_PERSONAL_API_KEY=<personal key> ./demo/run.sh
```

It sends with the ingest key and reads the logs and metric back with the personal key, an `ldp_` key with logs:read and metrics:read restricted to the same domain.
The ingest key is the one shared by the "SDK smoke tests" domain, and the gem sends as the `ruby-sdk` app.
`LOGDASH_VERSION=<version>` checks that version instead of the latest, and waits until RubyGems serves it.
It needs Docker, curl and jq.
25 changes: 9 additions & 16 deletions demo/demo_script.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,17 @@
# frozen_string_literal: true

require 'logdash'
require 'rubygems'

puts '=== LogDash SDK Demo ==='
seed = ENV.fetch('LOGDASH_SEED')
puts "=== LogDash SDK Demo: logdash #{Logdash::VERSION}, seed #{seed} ==="

# Get and display the logdash gem version
logdash_version = Gem.loaded_specs['logdash']&.version || 'unknown'
puts "Using logdash gem version: #{logdash_version}"
puts

api_key = ENV.fetch('LOGDASH_API_KEY', nil)
puts "Using API Key: #{api_key}"
logdash = Logdash.create(api_key: api_key, verbose: true)
logdash = Logdash.create(api_key: ENV.fetch('LOGDASH_API_KEY'), app: 'ruby-sdk', verbose: true)
logger = logdash[:logger]
metrics = logdash[:metrics]

logger.info('This is an info log')
logger.error('This is an error log')
metrics.set('demo_users', 42)
metrics.mutate('demo_counter', 1)

sleep 1
# Each call returns the thread that sends it, which Ruby kills when the script ends, so the script waits for them.
# run.sh reads back a log of every level and demo_users at seed + 1, so the mutate goes out after the set arrived.
levels = %w[info error warn debug verbose http silly]
levels.map { |level| logger.public_send(level, 'smoke test', seed) }.each(&:join)
metrics.set('demo_users', seed.to_i).join
metrics.mutate('demo_users', 1).join
58 changes: 46 additions & 12 deletions demo/run.sh
Original file line number Diff line number Diff line change
@@ -1,17 +1,51 @@
#!/bin/sh
set -e
# Installs the logdash gem from RubyGems, LOGDASH_VERSION or the latest, sends logs and a metric with it,
# and reads them back from the API. Needs Docker, curl and jq.
set -eu

echo "Building LogDash demo Docker image (using published gem)..."
docker build --no-cache -t logdash-demo -f demo/Dockerfile .
: "${LOGDASH_API_KEY:?must be the ingest key of the SDK smoke tests domain}"
api=https://api.logdash.io
seed=$(date +%s) # new every run, so the read-back finds what this run sent and nothing older

echo
echo "Running LogDash demo..."
echo
# RubyGems serves a version a little after its push, so installing one retries for up to 10 minutes.
attempt=1
until docker build --no-cache --build-arg LOGDASH_VERSION="${LOGDASH_VERSION:-}" -t logdash-demo -f demo/Dockerfile .; do
if [ -z "${LOGDASH_VERSION:-}" ] || [ "$attempt" -ge 20 ]; then exit 1; fi
echo "logdash $LOGDASH_VERSION does not install yet, retrying in 30s"
attempt=$((attempt + 1))
sleep 30
done

# Run in non-interactive mode which works everywhere
docker run --rm \
-e LOGDASH_API_KEY="${LOGDASH_API_KEY:-YOUR_API_KEY_HERE}" \
logdash-demo
docker run --rm -e LOGDASH_API_KEY -e LOGDASH_SEED="$seed" logdash-demo

echo
echo "Demo completed!"
# A read-only personal key restricted to the smoke-test domain, which whoami names.
auth="Authorization: Bearer ${LOGDASH_PERSONAL_API_KEY:-}"
if ! whoami=$(curl -sS --fail-with-body -H "$auth" "$api/personal-api-keys/whoami") ||
! domain=$(printf '%s' "$whoami" | jq -er '.access | select(.kind == "clusters" and (.ids | length) == 1) | .ids[0]'); then
access=$(printf '%s' "$whoami" | jq -c '.access' 2>/dev/null || echo 'no access in the response')
echo "LOGDASH_PERSONAL_API_KEY must be a personal API key restricted to the SDK smoke tests domain. Its access: $access" >&2
exit 1
fi

# Logs and metrics show up a few seconds after they arrive.
levels='["debug", "error", "http", "info", "silly", "verbose", "warning"]'
logs='' metrics='' attempt=1
until
logs=$(curl -sS --fail-with-body -G -H "$auth" --data-urlencode "searchString=$seed" -d apps=ruby-sdk -d limit=100 \
"$api/clusters/$domain/logs") &&
metrics=$(curl -sS --fail-with-body -H "$auth" "$api/clusters/$domain/metrics") &&
printf '%s' "$logs" | jq -e --arg m "smoke test $seed" --argjson levels "$levels" \
'[.[] | select(.app == "ruby-sdk" and .message == $m) | .level] | unique == $levels' >/dev/null &&
printf '%s' "$metrics" | jq -e --argjson v "$((seed + 1))" \
'any(.[]; .app == "ruby-sdk" and .name == "demo_users" and .value == $v)' >/dev/null
do
if [ "$attempt" -ge 30 ]; then
echo "Did not read back a log of every level and demo_users = $((seed + 1)); both keys must be of one domain." >&2
echo "Logs: $logs" >&2
echo "Metrics: $metrics" >&2
exit 1
fi
attempt=$((attempt + 1))
sleep 2
done
echo "Read back a log of every level and demo_users = $((seed + 1))"
3 changes: 2 additions & 1 deletion lib/logdash/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

module Logdash
VERSION = '1.0.2'
# CI writes the released version here before it builds the gem; the v<version> tags are the source of truth
VERSION = '0.0.0'
end
Binary file removed logdash-1.0.2.gem
Binary file not shown.
4 changes: 2 additions & 2 deletions logdash.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ Gem::Specification.new do |spec|

spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = 'https://github.com/logdash-io/ruby-sdk'
spec.metadata['changelog_uri'] = 'https://github.com/logdash-io/ruby-sdk/blob/main/CHANGELOG.md'
spec.metadata['changelog_uri'] = 'https://github.com/logdash-io/ruby-sdk/releases'

spec.files = Dir.glob('lib/**/*.rb') + %w[LICENSE README.md CHANGELOG.md .rubocop.yml]
spec.files = Dir.glob('lib/**/*.rb') + %w[LICENSE README.md]
spec.bindir = 'exe'
spec.executables = []
spec.require_paths = ['lib']
Expand Down
Loading
Loading