From fca4165946158fce4d6e9166b1abad20a36accad Mon Sep 17 00:00:00 2001 From: dAnte Date: Tue, 18 Nov 2025 02:10:10 +0300 Subject: [PATCH 1/2] add support oracle tns parser for DbConnDetails --- instrumentation_sql.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/instrumentation_sql.go b/instrumentation_sql.go index f5c1fbe35..529abe27a 100644 --- a/instrumentation_sql.go +++ b/instrumentation_sql.go @@ -371,6 +371,7 @@ func ParseDBConnDetails(connStr string) DbConnDetails { parseMySQLConnDetailsKV, parseRedisConnString, parseDBConnDetailsURI, + parseOracleTNSSQLDriver, } for _, parseFn := range strategies { if details, ok := parseFn(connStr); ok { @@ -662,6 +663,44 @@ func parseRedisConnString(connStr string) (DbConnDetails, bool) { return d, false } +var ( + myOracleTNSGoDriverRe = regexp.MustCompile(`(?i)^([^/@]+)(?:/[^@]*)?@.*?host=([^)(]+).*?port=(\d+)`) + oracleTnsSqlKVPasswordRegex = regexp.MustCompile(`(?i)^([^/@]+)/[^@]*@`) +) + +func parseOracleTNSSQLDriver(connStr string) (DbConnDetails, bool) { + + matches := myOracleTNSGoDriverRe.FindAllStringSubmatch(connStr, -1) + + if len(matches) == 0 { + return DbConnDetails{}, false + } + + values := matches[0] + + host := values[2] + port := values[3] + + if host == "" { + host = "localhost" + } + + if port == "" { + port = "1521" + } + + d := DbConnDetails{ + User: values[1], + Host: host, + Port: port, + DatabaseName: "oracle", + } + + d.RawString = oracleTnsSqlKVPasswordRegex.ReplaceAllString(connStr, `${1}/***@`) + + return d, true +} + type dsnConnector struct { dsn string driver driver.Driver From 4c965eac02cd78d727364d44fbf34c1c1b215dac Mon Sep 17 00:00:00 2001 From: Evstigneev Denis Date: Fri, 28 Nov 2025 21:45:00 +0300 Subject: [PATCH 2/2] added example sqlx-oracle --- example/sqlx-oracle/Dockerfile | 20 ++++ example/sqlx-oracle/docker-compose.yaml | 98 +++++++++++++++++ example/sqlx-oracle/go.mod | 21 ++++ example/sqlx-oracle/go.sum | 59 +++++++++++ example/sqlx-oracle/main.go | 135 ++++++++++++++++++++++++ 5 files changed, 333 insertions(+) create mode 100644 example/sqlx-oracle/Dockerfile create mode 100644 example/sqlx-oracle/docker-compose.yaml create mode 100644 example/sqlx-oracle/go.mod create mode 100644 example/sqlx-oracle/go.sum create mode 100644 example/sqlx-oracle/main.go diff --git a/example/sqlx-oracle/Dockerfile b/example/sqlx-oracle/Dockerfile new file mode 100644 index 000000000..bce34fde4 --- /dev/null +++ b/example/sqlx-oracle/Dockerfile @@ -0,0 +1,20 @@ +FROM oraclelinux:8-slim + +RUN microdnf install -y wget unzip libaio golang git && \ + microdnf clean all + +WORKDIR /tmp +RUN wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-basic-linuxx64.zip && \ + unzip instantclient-basic-linuxx64.zip && \ + mkdir -p /opt && \ + mv instantclient_* /opt/instantclient && \ + rm -f instantclient-basic-linuxx64.zip + +WORKDIR /app + +COPY . . + +ENV LD_LIBRARY_PATH=/opt/instantclient +RUN CGO_ENABLED=1 go build -o app main.go + +CMD ["./app"] \ No newline at end of file diff --git a/example/sqlx-oracle/docker-compose.yaml b/example/sqlx-oracle/docker-compose.yaml new file mode 100644 index 000000000..de278e6c2 --- /dev/null +++ b/example/sqlx-oracle/docker-compose.yaml @@ -0,0 +1,98 @@ +services: + oracle-primary: + image: gvenzl/oracle-free:latest + container_name: hostdb1 + hostname: hostdb1 + ports: + - "1521:1521" + environment: + - ORACLE_PASSWORD=OraclePass123 + - APP_USER=scott + - APP_USER_PASSWORD=tiger + volumes: + - oracle_primary_data:/opt/oracle/oradata + healthcheck: + test: ["CMD", "healthcheck.sh"] + interval: 30s + timeout: 10s + retries: 5 + networks: + app_network: + aliases: + - hostdb1 + + oracle-standby: + image: gvenzl/oracle-free:latest + container_name: hostdb2 + hostname: hostdb2 + ports: + - "1522:1521" + environment: + - ORACLE_PASSWORD=OraclePass123 + - APP_USER=scott + - APP_USER_PASSWORD=tiger + volumes: + - oracle_standby_data:/opt/oracle/oradata + healthcheck: + test: ["CMD", "healthcheck.sh"] + interval: 30s + timeout: 10s + retries: 5 + networks: + app_network: + aliases: + - hostdb2 + + instana-agent: + image: icr.io/instana/agent:latest + container_name: instana_agent + pid: "host" + privileged: true + ports: + - "42699:42699" + environment: + - INSTANA_AGENT_KEY=${INSTANA_AGENT_KEY} + - INSTANA_DOWNLOAD_KEY=${INSTANA_DOWNLOAD_KEY} + - INSTANA_AGENT_ENDPOINT=${INSTANA_AGENT_ENDPOINT:-ingress-red-saas.instana.io} + - INSTANA_AGENT_ENDPOINT_PORT=443 + - INSTANA_ZONE=oracle-test + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /dev:/dev + - /sys:/sys + - /var/log:/var/log + networks: + - app_network + + go-app: + build: + context: . + dockerfile: Dockerfile + container_name: go_oracle_app + ports: + - "8080:8080" + environment: + - ORACLE_CONNECTION_STRING=scott/tiger@(description=(CONNECT_TIMEOUT=40)(RETRY_COUNT=10)(TRANSPORT_CONNECT_TIMEOUT=3)(address_list=(address=(protocol=tcp)(host=hostdb1)(port=1521))(address=(protocol=tcp)(host=hostdb2)(port=1521))(failover=on)(load_balance=off))(connect_data=(service_name=FREEPDB1))) + - INSTANA_AGENT_HOST=instana-agent + - INSTANA_AGENT_PORT=42699 + - INSTANA_LOG_LEVEL=info + - LD_LIBRARY_PATH=/opt/instantclient + - DYLD_LIBRARY_PATH=/opt/instantclient + - PATH=/opt/instantclient:${PATH} + depends_on: + oracle-primary: + condition: service_healthy + oracle-standby: + condition: service_healthy + instana-agent: + condition: service_started + networks: + - app_network + +volumes: + oracle_primary_data: + oracle_standby_data: + +networks: + app_network: + driver: bridge \ No newline at end of file diff --git a/example/sqlx-oracle/go.mod b/example/sqlx-oracle/go.mod new file mode 100644 index 000000000..b242f2453 --- /dev/null +++ b/example/sqlx-oracle/go.mod @@ -0,0 +1,21 @@ +module sqlx-go-oracle.com + +go 1.23.0 +require ( + github.com/godror/godror v0.49.5 + github.com/instana/go-sensor v1.71.2 + github.com/jmoiron/sqlx v1.4.0 +) + +require ( + github.com/VictoriaMetrics/easyproto v0.1.4 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/godror/knownpb v0.3.0 // indirect + github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/looplab/fsm v1.0.3 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/example/sqlx-oracle/go.sum b/example/sqlx-oracle/go.sum new file mode 100644 index 000000000..d836a61b2 --- /dev/null +++ b/example/sqlx-oracle/go.sum @@ -0,0 +1,59 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/UNO-SOFT/zlog v0.8.1 h1:TEFkGJHtUfTRgMkLZiAjLSHALjwSBdw6/zByMC5GJt4= +github.com/UNO-SOFT/zlog v0.8.1/go.mod h1:yqFOjn3OhvJ4j7ArJqQNA+9V+u6t9zSAyIZdWdMweWc= +github.com/VictoriaMetrics/easyproto v0.1.4 h1:r8cNvo8o6sR4QShBXQd1bKw/VVLSQma/V2KhTBPf+Sc= +github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/godror/godror v0.49.5 h1:8w2LaxfH1bgrZwtT4OX3G//ld9AUDsZMxZu89NaCTLk= +github.com/godror/godror v0.49.5/go.mod h1:kTMcxZzRw73RT5kn9v3JkBK4kHI6dqowHotqV72ebU8= +github.com/godror/knownpb v0.3.0 h1:+caUdy8hTtl7X05aPl3tdL540TvCcaQA6woZQroLZMw= +github.com/godror/knownpb v0.3.0/go.mod h1:PpTyfJwiOEAzQl7NtVCM8kdPCnp3uhxsZYIzZ5PV4zU= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= +github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/instana/go-sensor v1.71.2 h1:xjAJiE2RrhRi0vMoK7I8jnL9TYabgyfKgTHUkqCsWwk= +github.com/instana/go-sensor v1.71.2/go.mod h1:wWLB5TQn5zd+XxZPLkaScMzRr74ymtptaDTPhrueDyM= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/looplab/fsm v1.0.3 h1:qtxBsa2onOs0qFOtkqwf5zE0uP0+Te+wlIvXctPKpcw= +github.com/looplab/fsm v1.0.3/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= +github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/example/sqlx-oracle/main.go b/example/sqlx-oracle/main.go new file mode 100644 index 000000000..8206a6fac --- /dev/null +++ b/example/sqlx-oracle/main.go @@ -0,0 +1,135 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "time" + + "github.com/godror/godror" + instana "github.com/instana/go-sensor" + "github.com/jmoiron/sqlx" +) + +var s instana.TracerLogger + +func init() { + s = instana.InitCollector(&instana.Options{ + Service: "Oracle app", + Tracer: instana.DefaultTracerOptions(), + LogLevel: instana.Info, + }) +} + +func getTNSConnString() string { + connStr := os.Getenv("ORACLE_CONNECTION_STRING") + if connStr == "" { + user := getEnvOrDefault("ORACLE_USER", "scott") + password := getEnvOrDefault("ORACLE_PASSWORD", "tiger") + + tnsDescriptor := `(description=(CONNECT_TIMEOUT=40)(RETRY_COUNT=10)(TRANSPORT_CONNECT_TIMEOUT=3)` + + `(address_list=(address=(protocol=tcp)(host=hostdb1)(port=1521))` + + `(address=(protocol=tcp)(host=hostdb2)(port=1521))` + + `(failover=on)(load_balance=off))` + + `(connect_data=(service_name=ods-domain)))` + + connStr = fmt.Sprintf("%s/%s@%s", user, password, tnsDescriptor) + } + + return connStr +} + +func getEnvOrDefault(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +func initDbConn(connStr string) (*sqlx.DB, error) { + driverName := "oracle" + + P, err := godror.ParseDSN(connStr) + if err != nil { + return nil, fmt.Errorf("failed to parse DSN: %w", err) + } + + gDrv := godror.NewConnector(P).Driver() + instana.InstrumentSQLDriver(s.LegacySensor(), driverName, gDrv) + + dbx, err := sqlx.Open(driverName+"_with_instana", connStr) + if err != nil { + return nil, fmt.Errorf("failed to open connection: %w", err) + } + + dbx.SetMaxOpenConns(10) + dbx.SetMaxIdleConns(5) + dbx.SetConnMaxLifetime(time.Hour) + dbx.SetConnMaxIdleTime(10 * time.Minute) + + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + if err = dbx.PingContext(ctx); err != nil { + return nil, fmt.Errorf("failed to ping database: %w", err) + } + + fmt.Println("Successfully connected to Oracle database") + return dbx, nil +} + +var dbConn *sqlx.DB + +func handler(w http.ResponseWriter, req *http.Request) { + var result string + err := dbConn.QueryRowContext(req.Context(), `SELECT 'Connected to Oracle!' FROM DUAL`).Scan(&result) + if err != nil { + http.Error(w, fmt.Sprintf("Query error: %v", err), http.StatusInternalServerError) + return + } + + _, _ = fmt.Fprintf(w, "Result: %s", result) +} + +func healthHandler(w http.ResponseWriter, req *http.Request) { + ctx, cancel := context.WithTimeout(req.Context(), 2*time.Second) + defer cancel() + + if err := dbConn.PingContext(ctx); err != nil { + http.Error(w, "Database unhealthy", http.StatusServiceUnavailable) + return + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "OK") +} + +func main() { + connStr := getTNSConnString() + fmt.Println("Connecting to Oracle...") + + var err error + dbConn, err = initDbConn(connStr) + if err != nil { + panic(fmt.Sprintf("Failed to initialize database connection: %v", err)) + } + defer dbConn.Close() + + fmt.Println("Waiting for Instana agent...") + for i := 0; i < 30; i++ { + if instana.Ready() { + fmt.Println("Instana agent ready") + break + } + time.Sleep(1 * time.Second) + } + + http.HandleFunc("/oracle", instana.TracingHandlerFunc(s, "/oracle", handler)) + http.HandleFunc("/health", healthHandler) + + fmt.Println("Server starting on :8080") + if err := http.ListenAndServe(":8080", nil); err != nil { + panic(err) + } +}