logtail-rails 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/actions/cleanup-sources/action.yml +33 -0
- data/.github/actions/cleanup-sources/run.sh +51 -0
- data/.github/actions/create-source/action.yml +46 -0
- data/.github/actions/create-source/run.sh +40 -0
- data/.github/actions/verify-logs/action.yml +47 -0
- data/.github/actions/verify-logs/run.sh +104 -0
- data/.github/workflows/e2e.yml +275 -0
- data/.github/workflows/main.yml +6 -20
- data/.github/workflows/notify_linear_failure.yml +153 -0
- data/e2e/example.sh +66 -0
- data/e2e/expected.json +109 -0
- data/e2e/installed.rb +20 -0
- data/e2e/plan.sh +112 -0
- data/example-project/Gemfile +2 -4
- data/example-project/README.md +9 -7
- data/example-project/app/controllers/example_controller.rb +14 -7
- data/example-project/app/views/example/index.html.erb +3 -1
- data/example-project/config/application.rb +5 -4
- data/example-project/config/routes.rb +1 -0
- data/lib/logtail-rails/version.rb +1 -1
- data/lib/logtail-rails.rb +4 -0
- metadata +13 -2
- data/gemfiles/rails-edge.gemfile +0 -13
data/.github/workflows/main.yml
CHANGED
|
@@ -4,8 +4,6 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
6
|
pull_request:
|
|
7
|
-
schedule:
|
|
8
|
-
- cron: '20 5 * * 1'
|
|
9
7
|
workflow_dispatch:
|
|
10
8
|
|
|
11
9
|
jobs:
|
|
@@ -17,9 +15,11 @@ jobs:
|
|
|
17
15
|
fail-fast: false
|
|
18
16
|
|
|
19
17
|
matrix:
|
|
18
|
+
# Released Ruby and Rails versions only. The E2E workflow (e2e.yml) covers the newest stable Ruby and Rails,
|
|
19
|
+
# ruby-head and Rails' main branch.
|
|
20
20
|
ruby-version:
|
|
21
21
|
- "4.0"
|
|
22
|
-
- "3"
|
|
22
|
+
- "3.4"
|
|
23
23
|
- "3.3"
|
|
24
24
|
- "3.2"
|
|
25
25
|
- "3.1"
|
|
@@ -29,7 +29,6 @@ jobs:
|
|
|
29
29
|
- "2.5"
|
|
30
30
|
- "truffleruby"
|
|
31
31
|
gemfile:
|
|
32
|
-
- "rails-edge"
|
|
33
32
|
- "rails-8.1"
|
|
34
33
|
- "rails-8.0"
|
|
35
34
|
- "rails-7.2"
|
|
@@ -41,19 +40,6 @@ jobs:
|
|
|
41
40
|
- "rails-5.1"
|
|
42
41
|
- "rails-5.0"
|
|
43
42
|
exclude:
|
|
44
|
-
- gemfile: "rails-edge"
|
|
45
|
-
ruby-version: "3.2"
|
|
46
|
-
- gemfile: "rails-edge"
|
|
47
|
-
ruby-version: "3.1"
|
|
48
|
-
- gemfile: "rails-edge"
|
|
49
|
-
ruby-version: "3.0"
|
|
50
|
-
- gemfile: "rails-edge"
|
|
51
|
-
ruby-version: "2.7"
|
|
52
|
-
- gemfile: "rails-edge"
|
|
53
|
-
ruby-version: "2.6"
|
|
54
|
-
- gemfile: "rails-edge"
|
|
55
|
-
ruby-version: "2.5"
|
|
56
|
-
|
|
57
43
|
- gemfile: "rails-8.1"
|
|
58
44
|
ruby-version: "3.1"
|
|
59
45
|
- gemfile: "rails-8.1"
|
|
@@ -98,7 +84,7 @@ jobs:
|
|
|
98
84
|
- gemfile: "rails-5.2"
|
|
99
85
|
ruby-version: "4.0"
|
|
100
86
|
- gemfile: "rails-5.2"
|
|
101
|
-
ruby-version: "3"
|
|
87
|
+
ruby-version: "3.4"
|
|
102
88
|
- gemfile: "rails-5.2"
|
|
103
89
|
ruby-version: "3.3"
|
|
104
90
|
- gemfile: "rails-5.2"
|
|
@@ -113,7 +99,7 @@ jobs:
|
|
|
113
99
|
- gemfile: "rails-5.1"
|
|
114
100
|
ruby-version: "4.0"
|
|
115
101
|
- gemfile: "rails-5.1"
|
|
116
|
-
ruby-version: "3"
|
|
102
|
+
ruby-version: "3.4"
|
|
117
103
|
- gemfile: "rails-5.1"
|
|
118
104
|
ruby-version: "3.3"
|
|
119
105
|
- gemfile: "rails-5.1"
|
|
@@ -128,7 +114,7 @@ jobs:
|
|
|
128
114
|
- gemfile: "rails-5.0"
|
|
129
115
|
ruby-version: "4.0"
|
|
130
116
|
- gemfile: "rails-5.0"
|
|
131
|
-
ruby-version: "3"
|
|
117
|
+
ruby-version: "3.4"
|
|
132
118
|
- gemfile: "rails-5.0"
|
|
133
119
|
ruby-version: "3.3"
|
|
134
120
|
- gemfile: "rails-5.0"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
name: "Notify Linear on failure"
|
|
2
|
+
|
|
3
|
+
# Files a Linear issue for a failed run: "E2E failure: <workflow name> (<owner/repo>)" at High priority, listing the
|
|
4
|
+
# failed jobs and steps with their errors, or with their summaries when job_summaries is set. When every failed job's
|
|
5
|
+
# name starts with "canary" (pre-release runtime or dependencies), the title says "E2E canary failure" and the
|
|
6
|
+
# priority is Low. While an issue with the same title is still open, the run is added to it as a comment instead,
|
|
7
|
+
# so a test that keeps failing every day makes one issue, not one per day.
|
|
8
|
+
# Secrets: LINEAR_API_KEY, LINEAR_TEAM_TELEMETRY_ID, LINEAR_STATUS_PLAN_ID, LINEAR_PROJECT_INGESTION_FOR_EVERYTHING_ID,
|
|
9
|
+
# LINEAR_ASSIGNEE_PETR_ID.
|
|
10
|
+
on:
|
|
11
|
+
workflow_call:
|
|
12
|
+
inputs:
|
|
13
|
+
job_summaries:
|
|
14
|
+
description: >-
|
|
15
|
+
Adds each failed job's summary to the issue, or its errors when it has none. A job hands its summary over as
|
|
16
|
+
an artifact named "e2e-summary-<job name>" holding summary.md.
|
|
17
|
+
type: boolean
|
|
18
|
+
default: false
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
create-linear-issue:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
# A caller must grant checks: read as well (the errors come from the annotations), or this job cannot start.
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
actions: read
|
|
27
|
+
checks: read
|
|
28
|
+
steps:
|
|
29
|
+
- name: "Gather failure context"
|
|
30
|
+
id: ctx
|
|
31
|
+
env:
|
|
32
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
RUN_ID: ${{ github.event.workflow_run.id || github.run_id }}
|
|
34
|
+
RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt || github.run_attempt }}
|
|
35
|
+
JOB_SUMMARIES: ${{ inputs.job_summaries }}
|
|
36
|
+
run: |
|
|
37
|
+
JOBS=$(gh api \
|
|
38
|
+
"repos/${{ github.repository }}/actions/runs/$RUN_ID/attempts/$RUN_ATTEMPT/jobs" \
|
|
39
|
+
--paginate \
|
|
40
|
+
--jq '.jobs[]
|
|
41
|
+
| select(.conclusion == "failure")
|
|
42
|
+
| {id, name, steps: ([.steps[] | select(.conclusion == "failure") | .name] | join(", "))}
|
|
43
|
+
| @json')
|
|
44
|
+
|
|
45
|
+
FAILED=""
|
|
46
|
+
while read -r job; do
|
|
47
|
+
[ -n "$job" ] || continue
|
|
48
|
+
ID=$(jq -r .id <<<"$job")
|
|
49
|
+
NAME=$(jq -r .name <<<"$job")
|
|
50
|
+
STEPS=$(jq -r .steps <<<"$job")
|
|
51
|
+
FAILED="$FAILED### $NAME"$'\n\n'"Failed at **${STEPS}**"$'\n\n'
|
|
52
|
+
# A job's summary already says why it failed. Otherwise, or when the job stopped before writing it, the
|
|
53
|
+
# issue gets its ::error:: lines, without the runner's closing "Process completed with exit code 1."
|
|
54
|
+
SUMMARY_DIR="summaries/$ID"
|
|
55
|
+
if [ "$JOB_SUMMARIES" = true ]; then
|
|
56
|
+
gh run download "$RUN_ID" -R "${{ github.repository }}" -n "e2e-summary-$NAME" -D "$SUMMARY_DIR" >/dev/null 2>&1 || true
|
|
57
|
+
fi
|
|
58
|
+
if [ -f "$SUMMARY_DIR/summary.md" ]; then
|
|
59
|
+
FAILED="$FAILED$(cat "$SUMMARY_DIR/summary.md")"$'\n\n'
|
|
60
|
+
else
|
|
61
|
+
ERRORS=$(gh api "repos/${{ github.repository }}/check-runs/$ID/annotations" \
|
|
62
|
+
--jq '[.[] | select(.annotation_level == "failure" and (.message | startswith("Process completed with exit code") | not)) | .message | gsub("\n"; " ")] | unique | .[] | "- \(.)"') || ERRORS=""
|
|
63
|
+
FAILED="$FAILED${ERRORS:+$ERRORS$'\n\n'}"
|
|
64
|
+
fi
|
|
65
|
+
done <<<"$JOBS"
|
|
66
|
+
# Canary only when there are failed jobs and all of them are canary jobs.
|
|
67
|
+
CANARY=false
|
|
68
|
+
if [ -n "$JOBS" ] && jq -se 'all(.[]; .name | startswith("canary"))' <<<"$JOBS" >/dev/null; then CANARY=true; fi
|
|
69
|
+
|
|
70
|
+
while [[ "$FAILED" == *$'\n' ]]; do FAILED="${FAILED%$'\n'}"; done
|
|
71
|
+
if [ -z "$FAILED" ]; then
|
|
72
|
+
FAILED="_(no failed jobs reported by the API yet)_"
|
|
73
|
+
elif [ "${#FAILED}" -gt 20000 ]; then
|
|
74
|
+
FAILED="${FAILED:0:20000}"$'\n\n'"_(cut at 20,000 characters, the rest is in the run)_"
|
|
75
|
+
fi
|
|
76
|
+
printf '%s\n' "$FAILED" > failed_jobs.md
|
|
77
|
+
echo "canary=$CANARY" >> "$GITHUB_OUTPUT"
|
|
78
|
+
|
|
79
|
+
- name: "Create the Linear issue, or comment on the open one with the same title"
|
|
80
|
+
env:
|
|
81
|
+
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
|
82
|
+
LINEAR_TEAM_ID: ${{ secrets.LINEAR_TEAM_TELEMETRY_ID }}
|
|
83
|
+
LINEAR_STATUS_PLAN_ID: ${{ secrets.LINEAR_STATUS_PLAN_ID }}
|
|
84
|
+
LINEAR_PROJECT_INGESTION_FOR_EVERYTHING_ID: ${{ secrets.LINEAR_PROJECT_INGESTION_FOR_EVERYTHING_ID }}
|
|
85
|
+
LINEAR_ASSIGNEE_PETR_ID: ${{ secrets.LINEAR_ASSIGNEE_PETR_ID }}
|
|
86
|
+
CANARY: ${{ steps.ctx.outputs.canary }}
|
|
87
|
+
REPOSITORY: ${{ github.repository }}
|
|
88
|
+
WORKFLOW: ${{ github.event.workflow_run.name || github.workflow }}
|
|
89
|
+
REF_NAME: ${{ github.event.workflow_run.head_branch || github.ref_name }}
|
|
90
|
+
RUN_URL: ${{ github.event.workflow_run.html_url || format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }}
|
|
91
|
+
RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt || github.run_attempt }}
|
|
92
|
+
EVENT_NAME: ${{ github.event.workflow_run.event || github.event_name }}
|
|
93
|
+
SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
|
|
94
|
+
COMMIT_MESSAGE: ${{ github.event.workflow_run.head_commit.message || github.event.head_commit.message }}
|
|
95
|
+
run: |
|
|
96
|
+
if [ "$CANARY" = true ]; then
|
|
97
|
+
TITLE="E2E canary failure: $WORKFLOW ($REPOSITORY)"; PRIORITY=4
|
|
98
|
+
else
|
|
99
|
+
TITLE="E2E failure: $WORKFLOW ($REPOSITORY)"; PRIORITY=2
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# head_commit is only populated on push events; skip the subject otherwise
|
|
103
|
+
COMMIT_LINE="**Commit:** \`${SHA:0:7}\`"
|
|
104
|
+
[ -z "$COMMIT_MESSAGE" ] || COMMIT_LINE="$COMMIT_LINE: $(printf '%s' "$COMMIT_MESSAGE" | head -n1)"
|
|
105
|
+
ATTEMPT_NOTE=""
|
|
106
|
+
[ "$RUN_ATTEMPT" = 1 ] || ATTEMPT_NOTE=" (attempt #$RUN_ATTEMPT)"
|
|
107
|
+
{
|
|
108
|
+
echo "**$WORKFLOW** failed on \`$REF_NAME\` via \`$EVENT_NAME\`$ATTEMPT_NOTE"
|
|
109
|
+
echo
|
|
110
|
+
echo "**Failing jobs:**"
|
|
111
|
+
echo
|
|
112
|
+
cat failed_jobs.md
|
|
113
|
+
echo
|
|
114
|
+
echo "$COMMIT_LINE"
|
|
115
|
+
echo
|
|
116
|
+
echo "[View failing run]($RUN_URL)"
|
|
117
|
+
} > description.md
|
|
118
|
+
|
|
119
|
+
linear() {
|
|
120
|
+
curl -sS --fail-with-body -X POST https://api.linear.app/graphql \
|
|
121
|
+
-H "Authorization: $LINEAR_API_KEY" -H 'Content-Type: application/json' -d "$1"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Open means any state but done, canceled or marked as a duplicate. Better a second issue than none: when the
|
|
125
|
+
# lookup fails, a new issue is created.
|
|
126
|
+
FIND=$(jq -nc --arg title "$TITLE" --arg teamId "$LINEAR_TEAM_ID" '{
|
|
127
|
+
query: "query ($title: String!, $teamId: ID!) { issues(filter: {title: {eq: $title}, team: {id: {eq: $teamId}}, state: {type: {nin: [\"completed\", \"canceled\", \"duplicate\"]}}}, first: 1) { nodes { id url } } }",
|
|
128
|
+
variables: {title: $title, teamId: $teamId}
|
|
129
|
+
}')
|
|
130
|
+
FOUND=$(linear "$FIND") || { echo "::warning::looking for an open issue titled \"$TITLE\" failed: $FOUND"; FOUND='{}'; }
|
|
131
|
+
OPEN=$(jq -r '.data.issues.nodes[0] // empty | "\(.id) \(.url)"' <<<"$FOUND")
|
|
132
|
+
|
|
133
|
+
if [ -n "$OPEN" ]; then
|
|
134
|
+
read -r ISSUE_ID ISSUE_URL <<<"$OPEN"
|
|
135
|
+
PAYLOAD=$(jq -nc --arg issueId "$ISSUE_ID" --rawfile body description.md '{
|
|
136
|
+
query: "mutation ($input: CommentCreateInput!) { commentCreate(input: $input) { success } }",
|
|
137
|
+
variables: {input: {issueId: $issueId, body: $body}}
|
|
138
|
+
}')
|
|
139
|
+
RES=$(linear "$PAYLOAD") || true
|
|
140
|
+
jq -e '.data.commentCreate.success' <<<"$RES" >/dev/null || { echo "::error::commenting on $ISSUE_URL failed: $RES"; exit 1; }
|
|
141
|
+
echo "added this run to the open issue $ISSUE_URL"
|
|
142
|
+
else
|
|
143
|
+
PAYLOAD=$(jq -nc --arg title "$TITLE" --rawfile description description.md --arg teamId "$LINEAR_TEAM_ID" \
|
|
144
|
+
--arg assigneeId "$LINEAR_ASSIGNEE_PETR_ID" --arg stateId "$LINEAR_STATUS_PLAN_ID" \
|
|
145
|
+
--arg projectId "$LINEAR_PROJECT_INGESTION_FOR_EVERYTHING_ID" --argjson priority "$PRIORITY" '{
|
|
146
|
+
query: "mutation ($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { url } } }",
|
|
147
|
+
variables: {input: {title: $title, description: $description, teamId: $teamId, assigneeId: $assigneeId,
|
|
148
|
+
stateId: $stateId, projectId: $projectId, priority: $priority}}
|
|
149
|
+
}')
|
|
150
|
+
RES=$(linear "$PAYLOAD") || true
|
|
151
|
+
ISSUE_URL=$(jq -er '.data.issueCreate.issue.url' <<<"$RES") || { echo "::error::creating the issue failed: $RES"; exit 1; }
|
|
152
|
+
echo "created $ISSUE_URL"
|
|
153
|
+
fi
|
data/e2e/example.sh
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Installs or runs the example project (example-project/, a Rails app) the way its README tells a customer to, with the
|
|
3
|
+
# leg's Rails, logtail and logtail-rack versions. The output goes to out/<command>.log as well, and a failure ends with
|
|
4
|
+
# one ::error:: line giving the reason.
|
|
5
|
+
# e2e/example.sh install pins the leg's versions in the example's Gemfile and runs bundle install
|
|
6
|
+
# e2e/example.sh run starts bin/rails server, requests /send_logs and /raise_error, and stops the server with
|
|
7
|
+
# Ctrl-C (SIGINT); the logs go to BETTER_STACK_INGESTING_HOST with BETTER_STACK_SOURCE_TOKEN
|
|
8
|
+
# Env: RAILS (a release, or main with RAILS_REF the commit), SQLITE3 (a requirement, or empty for the Gemfile's own),
|
|
9
|
+
# LOGTAIL and LOGTAIL_RACK (releases, or empty for whatever Bundler picks), CLIENT (the logtail-rails release; on
|
|
10
|
+
# pushes and pull requests e2e.yml has installed the gem built from the checkout under that version).
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
14
|
+
OUT=$ROOT/out
|
|
15
|
+
LOG=$OUT/$1.log
|
|
16
|
+
PORT=3000
|
|
17
|
+
mkdir -p "$OUT"
|
|
18
|
+
cd "$ROOT/example-project"
|
|
19
|
+
|
|
20
|
+
# Replaces gem $1's line in the Gemfile, or adds one, with the requirement $2.
|
|
21
|
+
pin() {
|
|
22
|
+
sed -i.orig "/^gem \"$1\"/d" Gemfile && rm Gemfile.orig
|
|
23
|
+
echo "gem \"$1\", $2" >> Gemfile
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
case "$1" in
|
|
27
|
+
install)
|
|
28
|
+
cp Gemfile "$OUT/Gemfile.example"
|
|
29
|
+
if [ "$RAILS" = main ]; then pin rails "github: \"rails/rails\", ref: \"$RAILS_REF\""; else pin rails "\"= $RAILS\""; fi
|
|
30
|
+
if [ -n "$SQLITE3" ]; then pin sqlite3 "\"$SQLITE3\""; fi
|
|
31
|
+
# The example's Gemfile only names logtail-rails, logtail and logtail-rack come with it as its dependencies.
|
|
32
|
+
if [ -n "$LOGTAIL" ]; then pin logtail "\"= $LOGTAIL\""; fi
|
|
33
|
+
if [ -n "$LOGTAIL_RACK" ]; then pin logtail-rack "\"= $LOGTAIL_RACK\""; fi
|
|
34
|
+
pin logtail-rails "\"= $CLIENT\""
|
|
35
|
+
{ diff -u "$OUT/Gemfile.example" Gemfile || true; } | tee "$LOG"
|
|
36
|
+
bundle install 2>&1 | tee -a "$LOG" || {
|
|
37
|
+
echo "::error::bundle install failed for Rails $RAILS${RAILS_REF:+ $RAILS_REF} on Ruby $(ruby -e 'print RUBY_VERSION'): $(grep -m 1 -iE 'could not|error|incompatible|requires' "$LOG" || tail -n 1 "$LOG")"
|
|
38
|
+
exit 1
|
|
39
|
+
} ;;
|
|
40
|
+
run)
|
|
41
|
+
bin/rails server -p "$PORT" > "$LOG" 2>&1 &
|
|
42
|
+
SERVER=$!
|
|
43
|
+
# Up when it accepts connections. Polling a page instead would log requests of its own.
|
|
44
|
+
for _ in $(seq 120); do
|
|
45
|
+
if (: > "/dev/tcp/127.0.0.1/$PORT") 2>/dev/null; then break; fi
|
|
46
|
+
kill -0 "$SERVER" 2>/dev/null || { cat "$LOG"; echo "::error::bin/rails server exited before it listened: $(tail -n 1 "$LOG")"; exit 1; }
|
|
47
|
+
sleep 1
|
|
48
|
+
done
|
|
49
|
+
FAILED=""
|
|
50
|
+
SEND_LOGS=$(curl -sS -m 30 -o "$OUT/send_logs.html" -w '%{http_code}' "http://127.0.0.1:$PORT/send_logs") || SEND_LOGS=000
|
|
51
|
+
[ "$SEND_LOGS" = 200 ] || FAILED="GET /send_logs answered HTTP $SEND_LOGS, not 200"
|
|
52
|
+
# The action raises, Rails answers with its error page.
|
|
53
|
+
RAISE_ERROR=$(curl -sS -m 30 -o "$OUT/raise_error.html" -w '%{http_code}' "http://127.0.0.1:$PORT/raise_error") || RAISE_ERROR=000
|
|
54
|
+
[ "$RAISE_ERROR" = 500 ] || FAILED="${FAILED:+$FAILED, }GET /raise_error answered HTTP $RAISE_ERROR, not 500"
|
|
55
|
+
# Ctrl-C, as a customer stops it. Puma stops gracefully and the HTTP log device sends what is still buffered
|
|
56
|
+
# when the process exits.
|
|
57
|
+
kill -INT "$SERVER"
|
|
58
|
+
STATUS=0
|
|
59
|
+
wait "$SERVER" || STATUS=$?
|
|
60
|
+
cat "$LOG"
|
|
61
|
+
[ "$STATUS" = 0 ] || FAILED="${FAILED:+$FAILED, }bin/rails server exited with $STATUS after Ctrl-C"
|
|
62
|
+
# The gem warns on stderr about a blank source token and about batches Better Stack rejected.
|
|
63
|
+
if grep -q '^Logtail: ' "$LOG"; then FAILED="${FAILED:+$FAILED, }$(grep -m 1 '^Logtail: ' "$LOG")"; fi
|
|
64
|
+
if [ -n "$FAILED" ]; then echo "::error::The example project failed: $FAILED"; exit 1; fi ;;
|
|
65
|
+
*) echo "::error::unknown command $1"; exit 1 ;;
|
|
66
|
+
esac
|
data/e2e/expected.json
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"since_rails": "8.1",
|
|
4
|
+
"level": "warn",
|
|
5
|
+
"message_prefix": "Generating image variants require the image_processing gem."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"level": "debug",
|
|
9
|
+
"message_prefix": " \u001b[1m\u001b[35m (",
|
|
10
|
+
"contains": {"sql_query_executed": {"sql": "CREATE TABLE \"schema_migrations\" (\"version\" varchar NOT NULL PRIMARY KEY)"}}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"level": "debug",
|
|
14
|
+
"message_prefix": " \u001b[1m\u001b[35m (",
|
|
15
|
+
"contains": {"sql_query_executed": {"sql": "CREATE TABLE \"ar_internal_metadata\" (\"key\" varchar NOT NULL PRIMARY KEY, \"value\" varchar, \"created_at\" datetime(6) NOT NULL, \"updated_at\" datetime(6) NOT NULL)"}}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"level": "debug",
|
|
19
|
+
"message_prefix": " \u001b[1m\u001b[36mActiveRecord::SchemaMigration Load (",
|
|
20
|
+
"contains": {"sql_query_executed": {"sql": "SELECT \"schema_migrations\".\"version\" FROM \"schema_migrations\" ORDER BY \"schema_migrations\".\"version\" ASC"}}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"level": "info",
|
|
24
|
+
"message": "Started GET \"/send_logs\"",
|
|
25
|
+
"contains": {"event": {"http_request_received": {"method": "GET", "path": "/send_logs", "host": "127.0.0.1", "port": 3000}}, "context": {"http": {"method": "GET", "path": "/send_logs", "remote_addr": "127.0.0.1"}}}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"level": "info",
|
|
29
|
+
"message": "Processing by ExampleController#send_logs as */*",
|
|
30
|
+
"contains": {"controller_called": {"controller": "ExampleController", "action": "send_logs"}, "context": {"http": {"path": "/send_logs"}}}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"level": "debug",
|
|
34
|
+
"message": "Hey! I am debuging!",
|
|
35
|
+
"contains": {"context": {"runtime": {"file": "app/controllers/example_controller.rb", "line": 12, "frame_label": "send_logs"}, "http": {"path": "/send_logs"}}}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"level": "info",
|
|
39
|
+
"message": "You know what's interesting? Logtail !",
|
|
40
|
+
"contains": {"context": {"runtime": {"file": "app/controllers/example_controller.rb", "line": 15, "frame_label": "send_logs"}, "http": {"path": "/send_logs"}}}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"level": "warn",
|
|
44
|
+
"message": "Something might not be quite right...",
|
|
45
|
+
"contains": {"context": {"runtime": {"file": "app/controllers/example_controller.rb", "line": 18, "frame_label": "send_logs"}, "http": {"path": "/send_logs"}}}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"level": "error",
|
|
49
|
+
"message": "Oops! Something went wrong.",
|
|
50
|
+
"contains": {"context": {"runtime": {"file": "app/controllers/example_controller.rb", "line": 21, "frame_label": "send_logs"}, "http": {"path": "/send_logs"}}}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"level": "fatal",
|
|
54
|
+
"message": "Application crashed! Needs to be fixed ASAP!",
|
|
55
|
+
"contains": {"context": {"runtime": {"file": "app/controllers/example_controller.rb", "line": 24, "frame_label": "send_logs"}, "http": {"path": "/send_logs"}}}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"level": "info",
|
|
59
|
+
"message": "log message with tags",
|
|
60
|
+
"contains": {"tags": ["tag1", "tag2"], "context": {"runtime": {"file": "app/controllers/example_controller.rb", "line": 28, "frame_label": "send_logs"}, "http": {"path": "/send_logs"}}}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"level": "warn",
|
|
64
|
+
"message": "log structured data",
|
|
65
|
+
"contains": {"name": {"first": "John", "last": "Smith"}, "id": 123456, "context": {"runtime": {"file": "app/controllers/example_controller.rb", "line": 32, "frame_label": "send_logs"}, "http": {"path": "/send_logs"}}}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"since_rails": "8.1",
|
|
69
|
+
"level": "info",
|
|
70
|
+
"message_prefix": "[My first event] user_id=123 email=user@example.com at ",
|
|
71
|
+
"contains": {"message": "app/controllers/example_controller.rb:42", "event_name": "My first event", "payload": {"user_id": 123, "email": "user@example.com"}, "source_location": {"filepath": "app/controllers/example_controller.rb", "lineno": 42, "label": "send_logs"}, "context": {"http": {"path": "/send_logs"}}}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"since_rails": "8.1",
|
|
75
|
+
"level": "info",
|
|
76
|
+
"message_prefix": "[My tagged event with additional context] user_id=123 email=user@example.com at ",
|
|
77
|
+
"contains": {"message": "app/controllers/example_controller.rb:48", "event_name": "My tagged event with additional context", "payload": {"user_id": 123, "email": "user@example.com"}, "tags": ["api"], "source_location": {"filepath": "app/controllers/example_controller.rb", "lineno": 48, "label": "send_logs"}, "context": {"request_id": "abc123", "shop_id": 456, "http": {"path": "/send_logs"}}}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"level": "info",
|
|
81
|
+
"message_prefix": " Rendered example/send_logs.html.erb within layouts/application (",
|
|
82
|
+
"contains": {"template_rendered": {"name": "example/send_logs.html.erb"}, "context": {"http": {"path": "/send_logs"}}}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"level": "info",
|
|
86
|
+
"message_prefix": "Completed 200 OK in ",
|
|
87
|
+
"contains": {"event": {"http_response_sent": {"status": 200}}, "context": {"http": {"path": "/send_logs"}}}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"level": "info",
|
|
91
|
+
"message": "Started GET \"/raise_error\"",
|
|
92
|
+
"contains": {"event": {"http_request_received": {"method": "GET", "path": "/raise_error", "host": "127.0.0.1", "port": 3000}}, "context": {"http": {"method": "GET", "path": "/raise_error", "remote_addr": "127.0.0.1"}}}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"level": "info",
|
|
96
|
+
"message": "Processing by ExampleController#raise_error as */*",
|
|
97
|
+
"contains": {"controller_called": {"controller": "ExampleController", "action": "raise_error"}, "context": {"http": {"path": "/raise_error"}}}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"level": "error",
|
|
101
|
+
"message": "ArgumentError (Something went wrong, this action raised an exception!)",
|
|
102
|
+
"contains": {"error": {"name": "ArgumentError", "message": "Something went wrong, this action raised an exception!", "backtrace_json": ["app/controllers/example_controller.rb:55:in "]}, "context": {"http": {"path": "/raise_error"}}}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"level": "info",
|
|
106
|
+
"message_prefix": "Completed 500 Internal Server Error in ",
|
|
107
|
+
"contains": {"event": {"http_response_sent": {"status": 500}}, "context": {"http": {"path": "/raise_error"}}}
|
|
108
|
+
}
|
|
109
|
+
]
|
data/e2e/installed.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Prints the running Ruby and what the example's bundle resolved, as name=value lines for e2e.yml's version check.
|
|
2
|
+
# Run with bundle exec from example-project/.
|
|
3
|
+
rails = Gem.loaded_specs.fetch("rails")
|
|
4
|
+
client = Gem.loaded_specs.fetch("logtail-rails")
|
|
5
|
+
{
|
|
6
|
+
"ruby" => RUBY_VERSION,
|
|
7
|
+
"ruby_description" => RUBY_DESCRIPTION,
|
|
8
|
+
# ruby-head reports the next version with patch level -1
|
|
9
|
+
"ruby_dev" => RUBY_PATCHLEVEL == -1,
|
|
10
|
+
"rails" => rails.version,
|
|
11
|
+
# The commit of a Rails from GitHub, empty for a release
|
|
12
|
+
"rails_ref" => Bundler.locked_gems.specs.find { |spec| spec.name == "rails" }.source.options["revision"],
|
|
13
|
+
"logtail" => Gem.loaded_specs.fetch("logtail").version,
|
|
14
|
+
"logtail_rack" => Gem.loaded_specs.fetch("logtail-rack").version,
|
|
15
|
+
"sqlite3" => Gem.loaded_specs.fetch("sqlite3").version,
|
|
16
|
+
"client" => client.version,
|
|
17
|
+
"client_dir" => client.full_gem_path,
|
|
18
|
+
# The .gem file RubyGems installed it from, kept in its cache
|
|
19
|
+
"client_gem" => client.cache_file,
|
|
20
|
+
}.each { |name, value| puts "#{name}=#{value}" }
|
data/e2e/plan.sh
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Plans the E2E matrix: one job per leg, with the Ruby and the Rails, logtail and logtail-rack versions the leg runs the
|
|
3
|
+
# example project (example-project/, a Rails app) with. Prints the matrix as a JSON array, the include list of the test
|
|
4
|
+
# job in e2e.yml.
|
|
5
|
+
# floor the oldest Rails the example runs on (its config.load_defaults) at that line's newest patch, on the oldest
|
|
6
|
+
# Ruby both that Rails release and logtail-rails.gemspec allow, with logtail and logtail-rack at the gemspec's
|
|
7
|
+
# floors
|
|
8
|
+
# rails7 the newest Rails 7 release on Ruby 3.2 with the newest logtail and logtail-rack: the step between the
|
|
9
|
+
# floor and the newest Rails
|
|
10
|
+
# latest the newest stable Ruby that ruby/setup-ruby can install, with the newest Rails, logtail and logtail-rack
|
|
11
|
+
# releases
|
|
12
|
+
# canary ruby-head with the current commit of Rails' main branch, and whatever logtail and logtail-rack Bundler picks
|
|
13
|
+
# Env: LEG (all, floor, rails7, latest or canary), CLIENT_FROM (checkout or rubygems), and RUBY_VERSION_INPUT and
|
|
14
|
+
# RAILS_VERSION_INPUT: an exact Ruby, and an exact Rails release or a commit of Rails' main branch, that replace the
|
|
15
|
+
# planned ones in every leg.
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
19
|
+
RUBYGEMS=https://rubygems.org/api/v1
|
|
20
|
+
# What ruby/setup-ruby@v1 can install. A Ruby released today is in it once setup-ruby supports it.
|
|
21
|
+
SETUP_RUBY_VERSIONS_URL=https://raw.githubusercontent.com/ruby/setup-ruby/v1/ruby-builder-versions.json
|
|
22
|
+
# Job names become artifact names (e2e-summary-<job name>), which must not contain any of these.
|
|
23
|
+
NOT_IN_NAMES='[:"<>|*?/\\]'
|
|
24
|
+
|
|
25
|
+
# A registry hiccup at schedule time should not file a Linear issue, so every lookup is retried.
|
|
26
|
+
get() { curl -fsS -m 60 --retry 3 --retry-delay 10 "$1"; }
|
|
27
|
+
# The newest release of gem $1: rubygems.org's "version" is the highest version that is not a pre-release.
|
|
28
|
+
newest() { get "$RUBYGEMS/gems/$1.json" | jq -er .version; }
|
|
29
|
+
# The X.Y.Z of gem $1's ">= X.Y.Z" in logtail-rails.gemspec.
|
|
30
|
+
gemspec_floor() { sed -n "s/.*add_runtime_dependency \"$1\", .*\">= \([0-9.]*\)\".*/\1/p" "$ROOT/logtail-rails.gemspec"; }
|
|
31
|
+
# The X.Y of a ">= X.Y.Z" requirement.
|
|
32
|
+
minor_of() { sed -n 's/^>= *\([0-9]*\.[0-9]*\).*/\1/p' <<<"$1"; }
|
|
33
|
+
# The higher of two versions.
|
|
34
|
+
higher() { printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n 1; }
|
|
35
|
+
|
|
36
|
+
RAILS_VERSIONS=$(get "$RUBYGEMS/versions/rails.json")
|
|
37
|
+
# The newest release of Rails line $1 (7.0, or 7 for all of Rails 7).
|
|
38
|
+
newest_rails_of() {
|
|
39
|
+
jq -er --arg line "$1." '[.[] | select(.prerelease | not) | .number | select(startswith($line))]
|
|
40
|
+
| sort_by(split(".") | map(tonumber)) | last // empty' <<<"$RAILS_VERSIONS"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# The client the example installs: the gem built from the checkout, or the newest release on rubygems.org.
|
|
44
|
+
if [ "${CLIENT_FROM:-checkout}" = rubygems ]; then
|
|
45
|
+
CLIENT=$(newest logtail-rails)
|
|
46
|
+
else
|
|
47
|
+
CLIENT=$(sed -n 's/.*VERSION = "\(.*\)".*/\1/p' "$ROOT/lib/logtail-rails/version.rb")
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
LEGS=$([ "${LEG:-all}" = all ] && echo "floor rails7 latest canary" || echo "$LEG")
|
|
51
|
+
MATRIX='[]'
|
|
52
|
+
for leg in $LEGS; do
|
|
53
|
+
rails_ref=""
|
|
54
|
+
case "$leg" in
|
|
55
|
+
floor)
|
|
56
|
+
# The example's config.load_defaults names the oldest Rails it boots on: older releases reject that version.
|
|
57
|
+
rails=$(newest_rails_of "$(sed -n 's/^ *config\.load_defaults \([0-9.]*\).*/\1/p' "$ROOT/example-project/config/application.rb")")
|
|
58
|
+
gem_ruby=$(minor_of "$(sed -n "s/.*required_ruby_version = '\(.*\)'.*/\1/p" "$ROOT/logtail-rails.gemspec")")
|
|
59
|
+
rails_ruby=$(minor_of "$(jq -r --arg rails "$rails" '.[] | select(.number == $rails) | .ruby_version' <<<"$RAILS_VERSIONS")")
|
|
60
|
+
ruby=$(higher "$gem_ruby" "$rails_ruby")
|
|
61
|
+
logtail=$(gemspec_floor logtail)
|
|
62
|
+
logtail_rack=$(gemspec_floor logtail-rack) ;;
|
|
63
|
+
rails7)
|
|
64
|
+
ruby=3.2
|
|
65
|
+
rails=$(newest_rails_of 7)
|
|
66
|
+
logtail=$(newest logtail)
|
|
67
|
+
logtail_rack=$(newest logtail-rack) ;;
|
|
68
|
+
latest)
|
|
69
|
+
# The newest X.Y.Z in setup-ruby's list of CRuby builds, which also holds previews, head and debug builds.
|
|
70
|
+
ruby=$(get "$SETUP_RUBY_VERSIONS_URL" | jq -er '.ruby | map(select(test("^[0-9]+\\.[0-9]+\\.[0-9]+$"))) | max_by(split(".") | map(tonumber))')
|
|
71
|
+
rails=$(newest rails)
|
|
72
|
+
logtail=$(newest logtail)
|
|
73
|
+
logtail_rack=$(newest logtail-rack) ;;
|
|
74
|
+
canary)
|
|
75
|
+
ruby=head
|
|
76
|
+
rails=main
|
|
77
|
+
rails_ref=$(git ls-remote https://github.com/rails/rails.git refs/heads/main | cut -f 1)
|
|
78
|
+
logtail=""
|
|
79
|
+
logtail_rack="" ;;
|
|
80
|
+
*) echo "unknown leg $leg" >&2; exit 1 ;;
|
|
81
|
+
esac
|
|
82
|
+
ruby=${RUBY_VERSION_INPUT:-$ruby}
|
|
83
|
+
if [[ "${RAILS_VERSION_INPUT:-}" =~ ^[0-9a-f]{7,40}$ ]]; then
|
|
84
|
+
rails=main
|
|
85
|
+
rails_ref=$RAILS_VERSION_INPUT
|
|
86
|
+
elif [ -n "${RAILS_VERSION_INPUT:-}" ]; then
|
|
87
|
+
rails=$RAILS_VERSION_INPUT
|
|
88
|
+
rails_ref=""
|
|
89
|
+
fi
|
|
90
|
+
for value in "$ruby" "$rails" "$CLIENT"; do
|
|
91
|
+
[ -n "$value" ] || { echo "the $leg leg could not be planned: ruby '$ruby', rails '$rails', client '$CLIENT'" >&2; exit 1; }
|
|
92
|
+
done
|
|
93
|
+
[ "$rails" != main ] || [ -n "$rails_ref" ] || { echo "no commit found on Rails' main branch" >&2; exit 1; }
|
|
94
|
+
# Rails 7.0 and 7.1's sqlite3 adapter requires sqlite3 ~> 1.4, while the example's Gemfile asks for 2.x.
|
|
95
|
+
sqlite3=""
|
|
96
|
+
if [ "$rails" != main ] && [ "$(higher "$rails" 7.2)" != "$rails" ]; then sqlite3="~> 1.4"; fi
|
|
97
|
+
|
|
98
|
+
MATRIX=$(jq -c --arg leg "$leg" --arg ruby "$ruby" --arg rails "$rails" --arg rails_ref "$rails_ref" --arg sqlite3 "$sqlite3" \
|
|
99
|
+
--arg logtail "$logtail" --arg logtail_rack "$logtail_rack" --arg source "${CLIENT_FROM:-checkout}" --arg client "$CLIENT" '. + [{
|
|
100
|
+
name: "\($leg) (Ruby \($ruby), Rails \(if $rails == "main" then "main " + $rails_ref[0:7] else $rails end), \(
|
|
101
|
+
if $logtail == "" then "newest logtail and logtail-rack" else "logtail \($logtail), logtail-rack \($logtail_rack)" end))",
|
|
102
|
+
leg: $leg, ruby: $ruby, rails: $rails, rails_ref: $rails_ref, sqlite3: $sqlite3,
|
|
103
|
+
logtail: $logtail, logtail_rack: $logtail_rack, source: $source, client: $client,
|
|
104
|
+
# The floor and the Rails 7 leg run on the oldest Ubuntu image GitHub offers, the other legs follow ubuntu-latest.
|
|
105
|
+
runner: (if $leg == "floor" or $leg == "rails7" then "ubuntu-24.04" else "ubuntu-latest" end)
|
|
106
|
+
}]' <<<"$MATRIX")
|
|
107
|
+
done
|
|
108
|
+
if jq -e --arg not "$NOT_IN_NAMES" 'any(.[]; .name | test($not))' <<<"$MATRIX" >/dev/null; then
|
|
109
|
+
echo "a job name contains one of $NOT_IN_NAMES, which artifact names do not allow: $(jq -c 'map(.name)' <<<"$MATRIX")" >&2
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
echo "$MATRIX"
|
data/example-project/Gemfile
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
source "https://rubygems.org"
|
|
2
2
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
3
3
|
|
|
4
|
-
ruby "3.3.4"
|
|
5
|
-
|
|
6
4
|
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
|
|
7
|
-
gem "rails", "~> 8.1.0
|
|
5
|
+
gem "rails", "~> 8.1.0"
|
|
8
6
|
|
|
9
7
|
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
|
|
10
8
|
gem "sprockets-rails"
|
|
@@ -71,4 +69,4 @@ group :test do
|
|
|
71
69
|
gem "webdrivers"
|
|
72
70
|
end
|
|
73
71
|
|
|
74
|
-
gem "logtail-rails", "~> 0.
|
|
72
|
+
gem "logtail-rails", "~> 0.3"
|
data/example-project/README.md
CHANGED
|
@@ -9,23 +9,23 @@ You can download the example project from GitHub directly or you can clone it to
|
|
|
9
9
|
bundle install
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
This will install all dependencies listed in the `Gemfile.
|
|
12
|
+
This will install all dependencies listed in the `Gemfile`. The example runs on Rails 7.1 and newer.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
`config/application.rb` reads your source token and ingesting host from the environment. You can find them by going to [Sources](https://telemetry.betterstack.com/team/0/sources) -> Configure in Better Stack.
|
|
15
15
|
|
|
16
16
|
```ruby
|
|
17
17
|
config.logger = Logtail::Logger.create_default_logger(
|
|
18
|
-
"
|
|
19
|
-
ingesting_host: "
|
|
18
|
+
ENV["BETTER_STACK_SOURCE_TOKEN"],
|
|
19
|
+
ingesting_host: ENV["BETTER_STACK_INGESTING_HOST"],
|
|
20
20
|
)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Run the example project
|
|
24
24
|
|
|
25
|
-
To run the example application, run the following command:
|
|
25
|
+
To run the example application, run the following command with your source token and ingesting host:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
rails server
|
|
28
|
+
BETTER_STACK_SOURCE_TOKEN=<source_token> BETTER_STACK_INGESTING_HOST=<ingesting_host> bin/rails server
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
This will open a local server [127.0.0.1:3000.](http://127.0.0.1:3000/) On the main page, click the "Let's go!" button to generate test logs.
|
|
@@ -37,7 +37,9 @@ All done!
|
|
|
37
37
|
Log into your Logtail account to check your logs.
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
This will create a
|
|
40
|
+
This will create a log for each log level, one with tags and one with additional structured data, and on Rails 8.1 and newer two events reported with `Rails.event`, next to the request, controller and template logs of the Rails integration. Click "Raise an exception" on the main page to see how an exception raised in a controller action is logged. You can review these logs in Better Stack.
|
|
41
|
+
|
|
42
|
+
Stop the server with Ctrl-C. The logger sends what it still holds before the server exits.
|
|
41
43
|
|
|
42
44
|
# Logging
|
|
43
45
|
|
|
@@ -38,13 +38,20 @@ class ExampleController < ApplicationController
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
# Since Rails 8.1, you can also use the Rails.event to send events with structured data to Better Stack.
|
|
41
|
-
Rails.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Rails.event.
|
|
41
|
+
if Rails.respond_to?(:event)
|
|
42
|
+
Rails.event.notify("My first event", user_id: 123, email: "user@example.com")
|
|
43
|
+
|
|
44
|
+
# You can add context to all events
|
|
45
|
+
Rails.event.set_context(request_id: "abc123", shop_id: 456)
|
|
46
|
+
# And tags specific events
|
|
47
|
+
Rails.event.tagged("api") do
|
|
48
|
+
Rails.event.notify("My tagged event with additional context", user_id: 123, email: "user@example.com")
|
|
49
|
+
end
|
|
48
50
|
end
|
|
49
51
|
end
|
|
52
|
+
|
|
53
|
+
# Exceptions raised in an action are sent to Better Stack too, with their class, message and backtrace
|
|
54
|
+
def raise_error
|
|
55
|
+
raise ArgumentError, "Something went wrong, this action raised an exception!"
|
|
56
|
+
end
|
|
50
57
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
<h1>Hello from Logtail</h1>
|
|
2
2
|
<p>Click the button below to send some example logs</p>
|
|
3
|
-
<%= button_to "Let's go!", send_logs_path, method: :get %>
|
|
3
|
+
<%= button_to "Let's go!", send_logs_path, method: :get %>
|
|
4
|
+
<p>Or raise an exception in a controller action</p>
|
|
5
|
+
<%= button_to "Raise an exception", raise_error_path, method: :get %>
|