Skip to content

Commit e871f34

Browse files
authored
feat: opentelemetry + logging (#311)
This patch adds support for opentelemetry tracing as well as better logging. The included docker-compose includes grafana tempo to view the opentelemetry traces
1 parent 2b2ada6 commit e871f34

22 files changed

+398
-56
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ result
3131
/priv/plts/*.plt
3232
/priv/plts/*.plt.hash
3333
node_modules
34+
35+
tempo-data

Diff for: bin/start

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
cd "$(dirname "$0")"/.. || exit 1
44

5-
elixir -S mix run --no-halt -e "Application.ensure_all_started(:next_ls)" -- "$@"
5+
mix run --no-halt -e "Application.ensure_all_started(:next_ls)" -- "$@"

Diff for: config/config.exs

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import Config
22

33
config :next_ls, :indexing_timeout, 100
44

5-
config :logger, :default_handler, config: [type: :standard_error]
5+
config :logger, :default_handler,
6+
config: [
7+
file: ~c".elixir-tools/next-ls.log",
8+
filesync_repeat_interval: 5000,
9+
file_check: 5000,
10+
max_no_bytes: 10_000_000,
11+
max_no_files: 5,
12+
compress_on_rotate: true
13+
]
14+
15+
config :logger, :default_formatter, format: "\n$time $metadata[$level] $message\n", metadata: [:id]
616

717
import_config "#{config_env()}.exs"

Diff for: config/runtime.exs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Config
2+
3+
if System.get_env("NEXTLS_OTEL") == "1" do
4+
config :next_ls,
5+
otel: true
6+
7+
config :opentelemetry_exporter,
8+
otlp_protocol: :grpc,
9+
otlp_endpoint: "http://localhost:4317"
10+
else
11+
config :opentelemetry, traces_exporter: :none
12+
end

Diff for: config/test.exs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
import Config
22

3+
config :logger, :default_handler, config: [type: :standard_error]
4+
35
config :gen_lsp, :exit_on_end, false

Diff for: docker-compose.yml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
version: "3"
2+
services:
3+
tempo:
4+
image: grafana/tempo:latest
5+
command: [ "-config.file=/etc/tempo.yaml" ]
6+
volumes:
7+
- ./tempo.yaml:/etc/tempo.yaml
8+
- ./tempo-data:/tmp/tempo
9+
ports:
10+
- "14268:14268" # jaeger ingest
11+
- "3200:3200" # tempo
12+
# - "4317:4317" # otlp grpc
13+
# - "4318:4318" # otlp http
14+
- "9411:9411" # zipkin
15+
16+
otel-collector:
17+
image: otel/opentelemetry-collector:0.61.0
18+
command: [ "--config=/etc/otel-collector.yaml" ]
19+
volumes:
20+
- ./otel-collector.yaml:/etc/otel-collector.yaml
21+
ports:
22+
- "4317:4317" # otlp grpc
23+
- "4318:4318" # otlp http
24+
25+
prometheus:
26+
image: prom/prometheus:latest
27+
command:
28+
- --config.file=/etc/prometheus.yaml
29+
- --web.enable-remote-write-receiver
30+
- --enable-feature=exemplar-storage
31+
volumes:
32+
- ./prometheus.yaml:/etc/prometheus.yaml
33+
ports:
34+
- "9090:9090"
35+
36+
grafana:
37+
image: grafana/grafana:9.4.3
38+
volumes:
39+
- ./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml
40+
environment:
41+
- GF_AUTH_ANONYMOUS_ENABLED=true
42+
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
43+
- GF_AUTH_DISABLE_LOGIN_FORM=true
44+
- GF_FEATURE_TOGGLES_ENABLE=traceqlEditor
45+
ports:
46+
- "3000:3000"
47+

Diff for: flake.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
src = self.outPath;
6262
inherit version elixir;
6363
pname = "next-ls-deps";
64-
hash = "sha256-LV1DYmWi0Mcz1S5k77/jexXYqay7OpysCwOtUcafqGE=";
64+
hash = "sha256-M8BtmnSWpABqu8ZelZkzG1BOhD8sm3MoqXFIEgCy708=";
6565
};
6666

6767
BURRITO_ERTS_PATH = "${beamPackages.erlang}/lib/erlang";

Diff for: grafana-datasources.yaml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
apiVersion: 1
2+
3+
datasources:
4+
- name: Prometheus
5+
type: prometheus
6+
uid: prometheus
7+
access: proxy
8+
orgId: 1
9+
url: http://prometheus:9090
10+
basicAuth: false
11+
isDefault: false
12+
version: 1
13+
editable: false
14+
jsonData:
15+
httpMethod: GET
16+
- name: Tempo
17+
type: tempo
18+
access: proxy
19+
orgId: 1
20+
url: http://tempo:3200
21+
basicAuth: false
22+
isDefault: true
23+
version: 1
24+
editable: false
25+
apiVersion: 1
26+
uid: tempo
27+
jsonData:
28+
httpMethod: GET
29+
serviceMap:
30+
datasourceUid: prometheus

Diff for: lib/next_ls/application.ex

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ defmodule NextLS.Application do
77

88
@impl true
99
def start(_type, _args) do
10+
if Application.get_env(:next_ls, :otel, false) do
11+
NextLS.OpentelemetrySchematic.setup()
12+
NextLS.OpentelemetryGenLSP.setup()
13+
end
14+
1015
case System.cmd("epmd", ["-daemon"], stderr_to_stdout: true) do
1116
{_, 0} ->
1217
:ok

Diff for: lib/next_ls/db.ex

+51-34
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@ defmodule NextLS.DB do
44

55
import __MODULE__.Query
66

7+
alias OpenTelemetry.Tracer
8+
9+
require OpenTelemetry.Tracer
10+
711
@type query :: String.t()
812

913
def start_link(args) do
1014
GenServer.start_link(__MODULE__, args, Keyword.take(args, [:name]))
1115
end
1216

1317
@spec query(pid(), query(), list()) :: list()
14-
def query(server, query, opts \\ []), do: GenServer.call(server, {:query, query, opts}, :infinity)
18+
def query(server, query, opts \\ []) do
19+
ctx = OpenTelemetry.Ctx.get_current()
20+
GenServer.call(server, {:query, query, opts, ctx}, :infinity)
21+
end
1522

1623
@spec insert_symbol(pid(), map()) :: :ok
1724
def insert_symbol(server, payload), do: GenServer.cast(server, {:insert_symbol, payload})
@@ -43,28 +50,36 @@ defmodule NextLS.DB do
4350
}}
4451
end
4552

46-
def handle_call({:query, query, args_or_opts}, _from, %{conn: conn} = s) do
47-
{:message_queue_len, count} = Process.info(self(), :message_queue_len)
48-
NextLS.DB.Activity.update(s.activity, count)
49-
opts = if Keyword.keyword?(args_or_opts), do: args_or_opts, else: [args: args_or_opts]
50-
51-
query =
52-
if opts[:select] do
53-
String.replace(query, ":select", Enum.map_join(opts[:select], ", ", &to_string/1))
54-
else
55-
query
53+
def handle_call({:query, query, args_or_opts, ctx}, _from, %{conn: conn} = s) do
54+
token = OpenTelemetry.Ctx.attach(ctx)
55+
56+
try do
57+
Tracer.with_span :"db.query receive", %{attributes: %{query: query}} do
58+
{:message_queue_len, count} = Process.info(self(), :message_queue_len)
59+
NextLS.DB.Activity.update(s.activity, count)
60+
opts = if Keyword.keyword?(args_or_opts), do: args_or_opts, else: [args: args_or_opts]
61+
62+
query =
63+
if opts[:select] do
64+
String.replace(query, ":select", Enum.map_join(opts[:select], ", ", &to_string/1))
65+
else
66+
query
67+
end
68+
69+
rows =
70+
for row <- __query__({conn, s.logger}, query, opts[:args] || []) do
71+
if opts[:select] do
72+
opts[:select] |> Enum.zip(row) |> Map.new()
73+
else
74+
row
75+
end
76+
end
77+
78+
{:reply, rows, s}
5679
end
57-
58-
rows =
59-
for row <- __query__({conn, s.logger}, query, opts[:args] || []) do
60-
if opts[:select] do
61-
opts[:select] |> Enum.zip(row) |> Map.new()
62-
else
63-
row
64-
end
65-
end
66-
67-
{:reply, rows, s}
80+
after
81+
OpenTelemetry.Ctx.detach(token)
82+
end
6883
end
6984

7085
def handle_cast({:insert_symbol, symbol}, %{conn: conn} = s) do
@@ -190,22 +205,24 @@ defmodule NextLS.DB do
190205
end
191206

192207
def __query__({conn, logger}, query, args) do
193-
args = Enum.map(args, &cast/1)
208+
Tracer.with_span :"db.query process", %{attributes: %{query: query}} do
209+
args = Enum.map(args, &cast/1)
194210

195-
case Exqlite.Basic.exec(conn, query, args) do
196-
{:error, %{message: message, statement: statement}, _} ->
197-
NextLS.Logger.warning(logger, """
198-
sqlite3 error: #{message}
211+
case Exqlite.Basic.exec(conn, query, args) do
212+
{:error, %{message: message, statement: statement}, _} ->
213+
NextLS.Logger.warning(logger, """
214+
sqlite3 error: #{message}
199215
200-
statement: #{statement}
201-
arguments: #{inspect(args)}
202-
""")
216+
statement: #{statement}
217+
arguments: #{inspect(args)}
218+
""")
203219

204-
{:error, message}
220+
{:error, message}
205221

206-
result ->
207-
{:ok, rows, _} = Exqlite.Basic.rows(result)
208-
rows
222+
result ->
223+
{:ok, rows, _} = Exqlite.Basic.rows(result)
224+
rows
225+
end
209226
end
210227
end
211228

Diff for: lib/next_ls/db/schema.ex

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ defmodule NextLS.DB.Schema do
2929
# FIXME: this is odd tech debt. not a big deal but is confusing
3030
{_, logger} = conn
3131

32-
NextLS.Logger.log(logger, "Beginning DB migration...")
32+
NextLS.Logger.info(logger, "Beginning DB migration...")
3333

3434
DB.__query__(
3535
conn,
@@ -113,7 +113,7 @@ defmodule NextLS.DB.Schema do
113113
{:ok, :reindex}
114114
end
115115

116-
NextLS.Logger.log(logger, "Finished DB migration...")
116+
NextLS.Logger.info(logger, "Finished DB migration...")
117117
result
118118
end
119119
end

Diff for: lib/next_ls/extensions/credo_extension.ex

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ defmodule NextLS.CredoExtension do
3030
if settings.enable do
3131
Registry.register(registry, :extensions, :credo)
3232

33-
NextLS.Logger.log(logger, "[extension] Credo initializing with options #{inspect(settings)}")
33+
NextLS.Logger.info(logger, "[extension] Credo initializing with options #{inspect(settings)}")
3434

3535
{:ok,
3636
%{
@@ -44,7 +44,7 @@ defmodule NextLS.CredoExtension do
4444
refresh_refs: Map.new()
4545
}}
4646
else
47-
NextLS.Logger.log(logger, "[extension] Credo disabled")
47+
NextLS.Logger.info(logger, "[extension] Credo disabled")
4848
:ignore
4949
end
5050
end

Diff for: lib/next_ls/logger.ex

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ defmodule NextLS.Logger do
22
@moduledoc false
33
use GenServer
44

5+
require Logger
6+
57
def start_link(arg) do
68
GenServer.start_link(__MODULE__, arg, Keyword.take(arg, [:name]))
79
end
@@ -22,6 +24,14 @@ defmodule NextLS.Logger do
2224

2325
def handle_cast({:log, type, msg}, state) do
2426
apply(GenLSP, type, [state.lsp, String.trim("[NextLS] #{msg}")])
27+
28+
case type do
29+
:log -> Logger.debug(msg)
30+
:warning -> Logger.warning(msg)
31+
:error -> Logger.error(msg)
32+
:info -> Logger.info(msg)
33+
end
34+
2535
{:noreply, state}
2636
end
2737

Diff for: lib/next_ls/opentelemetry/gen_lsp.ex

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
defmodule NextLS.OpentelemetryGenLSP do
2+
@moduledoc false
3+
require Logger
4+
5+
@tracer_id __MODULE__
6+
7+
def setup do
8+
:ok =
9+
:telemetry.attach_many(
10+
"gen_lsp-handler",
11+
[
12+
[:gen_lsp, :notify, :server, :start],
13+
[:gen_lsp, :notify, :server, :stop],
14+
[:gen_lsp, :request, :server, :start],
15+
[:gen_lsp, :request, :server, :stop],
16+
[:gen_lsp, :request, :client, :start],
17+
[:gen_lsp, :request, :client, :stop],
18+
[:gen_lsp, :notification, :client, :start],
19+
[:gen_lsp, :notification, :client, :stop],
20+
[:gen_lsp, :handle_request, :start],
21+
[:gen_lsp, :handle_request, :stop],
22+
[:gen_lsp, :handle_notification, :start],
23+
[:gen_lsp, :handle_notification, :stop],
24+
[:gen_lsp, :handle_info, :start],
25+
[:gen_lsp, :handle_info, :stop]
26+
# [:gen_lsp, :buffer, :outgoing, :start],
27+
# [:gen_lsp, :buffer, :outgoing, :stop],
28+
# [:gen_lsp, :buffer, :incoming, :start],
29+
# [:gen_lsp, :buffer, :incoming, :stop]
30+
],
31+
&__MODULE__.process/4,
32+
nil
33+
)
34+
end
35+
36+
def process([:gen_lsp, type1, type2, :start], _measurements, metadata, _config) do
37+
OpentelemetryTelemetry.start_telemetry_span(
38+
@tracer_id,
39+
:"gen_lsp.#{type1}.#{type2}",
40+
metadata,
41+
%{kind: :server, attributes: metadata}
42+
)
43+
end
44+
45+
def process([:gen_lsp, handle, :start], _measurements, metadata, _config) do
46+
if handle in [:handle_request, :handle_notification] do
47+
# set attribute for parent span
48+
OpenTelemetry.Tracer.set_attribute(:method, metadata[:method])
49+
end
50+
51+
OpentelemetryTelemetry.start_telemetry_span(
52+
@tracer_id,
53+
:"next_ls.#{handle}",
54+
metadata,
55+
%{kind: :server, attributes: metadata}
56+
)
57+
end
58+
59+
def process([:gen_lsp, _, _, :stop], _measurements, metadata, _config) do
60+
OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata)
61+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata)
62+
end
63+
64+
def process([:gen_lsp, _, :stop], _measurements, metadata, _config) do
65+
OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata)
66+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata)
67+
end
68+
end

0 commit comments

Comments
 (0)