Skip to content

Commit e7c8c3c

Browse files
authoredJun 6, 2024
Release 1.3 (#96)
### Version 1.3.0, June 7, 2024 This release includes the following changes: - Alert logs can be exported for collection by a log reader like Promtail or FluentBit. Default output to `/log/alert.log` in JSON format. - Provide ability to connect as SYSDBA or SYSOPER by setting DB_ROLE. - New default metric is added to report the type of database connected to (CDB or PDB). - New default metrics are added for cache hit ratios. - Deafult metrics updated to suppress spurious warnings in log. - Wait class metric updated to use a better query. - The sample dashboard is updated to include new metrics. - Fixed a bug which prevented periodic freeing of memory. - Set CLIENT_INFO to a meaningful value. - Update Go toolchain to 1.22.4. - Updated some third-party dependencies. Thank you to the following people for their suggestions and contributions: - [@pioro](https://github.com/pioro) - [@savoir81](https://github.com/savoir81) --------- Signed-off-by: Mark Nelson <mark.x.nelson@oracle.com>
1 parent c72e967 commit e7c8c3c

16 files changed

+2152
-1351
lines changed
 

‎Dockerfile

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ ARG BASE_IMAGE
22
FROM ${BASE_IMAGE} AS build
33

44
RUN microdnf install wget gzip gcc && \
5-
wget https://go.dev/dl/go1.21.6.linux-amd64.tar.gz && \
5+
wget -q https://go.dev/dl/go1.22.4.linux-amd64.tar.gz && \
66
rm -rf /usr/local/go && \
7-
tar -C /usr/local -xzf go1.21.6.linux-amd64.tar.gz && \
8-
rm go1.21.6.linux-amd64.tar.gz
7+
tar -C /usr/local -xzf go1.22.4.linux-amd64.tar.gz && \
8+
rm go1.22.4.linux-amd64.tar.gz
99

1010
ENV PATH $PATH:/usr/local/go/bin
1111

@@ -25,7 +25,7 @@ LABEL org.opencontainers.image.description="Oracle Database Observability Export
2525
ENV VERSION ${VERSION:-1.0.0}
2626
ENV DEBIAN_FRONTEND=noninteractive
2727

28-
RUN microdnf install -y oracle-instantclient-release-el8 && microdnf install -y oracle-instantclient-basic
28+
RUN microdnf install -y oracle-instantclient-release-el8 && microdnf install -y oracle-instantclient-basic
2929

3030
ENV LD_LIBRARY_PATH /usr/lib/oracle/21/client64/lib
3131
ENV PATH $PATH:/usr/lib/oracle/21/client64/bin
@@ -34,6 +34,9 @@ ENV ORACLE_HOME /usr/lib/oracle/21/client64
3434
COPY --from=build /go/src/oracledb_exporter/oracle-db-appdev-monitoring /oracledb_exporter
3535
ADD ./default-metrics.toml /default-metrics.toml
3636

37+
# create the mount point for alert log exports (default location)
38+
RUN mkdir /log && chown 1000:1000 /log
39+
3740
EXPOSE 9161
3841

3942
USER 1000

‎LICENSE.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

2222
--
2323

24-
Copyright (c) 2021, 2023, Oracle and/or its affiliates.
24+
Copyright (c) 2021, 2024, Oracle and/or its affiliates.
2525

2626
The Universal Permissive License (UPL), Version 1.0
2727

‎Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ OS_TYPE ?= $(shell uname -s | tr '[:upper:]' '[:lower:]')
33
ARCH_TYPE ?= $(subst x86_64,amd64,$(patsubst i%86,386,$(ARCH)))
44
GOOS ?= $(shell go env GOOS)
55
GOARCH ?= $(shell go env GOARCH)
6-
VERSION ?= 1.2.1
6+
VERSION ?= 1.3.0
77
LDFLAGS := -X main.Version=$(VERSION)
88
GOFLAGS := -ldflags "$(LDFLAGS) -s -w"
99
BUILD_ARGS = --build-arg VERSION=$(VERSION)

‎README.md

+250-31
Large diffs are not rendered by default.

‎THIRD_PARTY_LICENSES.txt

+199-46
Large diffs are not rendered by default.

‎alertlog/alertlog.go

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) 2024, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.package vault
3+
4+
package alertlog
5+
6+
import (
7+
"database/sql"
8+
"encoding/json"
9+
"errors"
10+
"fmt"
11+
"io"
12+
"os"
13+
"strings"
14+
15+
"github.com/go-kit/log"
16+
"github.com/go-kit/log/level"
17+
)
18+
19+
type LogRecord struct {
20+
Timestamp string `json:"timestamp"`
21+
ModuleId string `json:"moduleId"`
22+
ECID string `json:"ecid"`
23+
Message string `json:"message"`
24+
}
25+
26+
func UpdateLog(logDestination string, logger log.Logger, db *sql.DB) {
27+
28+
// check if the log file exists, and if not, create it
29+
if _, err := os.Stat(logDestination); errors.Is(err, os.ErrNotExist) {
30+
level.Info(logger).Log("msg", "Log destination file does not exist, will try to create it: "+logDestination)
31+
f, e := os.Create(logDestination)
32+
if e != nil {
33+
level.Error(logger).Log("msg", "Failed to create the log file: "+logDestination)
34+
return
35+
}
36+
f.Close()
37+
}
38+
39+
// read the last line of the file to get the latest timestamp
40+
file, err := os.Open(logDestination)
41+
42+
if err != nil {
43+
level.Error(logger).Log("msg", "Could not open the alert log destination file: "+logDestination)
44+
return
45+
}
46+
47+
// create an empty line
48+
line := ""
49+
50+
// the file could be very large, so we will read backwards from the end of the file
51+
// until the first line break is found, or until we reach the start of the file
52+
var pointer int64 = 0
53+
stat, _ := file.Stat()
54+
filesize := stat.Size()
55+
56+
for {
57+
if filesize == 0 {
58+
break
59+
}
60+
61+
pointer -= 1
62+
file.Seek(pointer, io.SeekEnd)
63+
64+
char := make([]byte, 1)
65+
file.Read(char)
66+
67+
if pointer != -1 && (char[0] == 10 || char[0] == 13) {
68+
// we found a new line
69+
break
70+
}
71+
72+
line = fmt.Sprintf("%s%s", string(char), line)
73+
74+
if pointer == -filesize {
75+
// we got all the way to the start of the file
76+
break
77+
}
78+
}
79+
80+
// if we got an empty line, then set a reasonable default
81+
if len(line) <= 1 {
82+
// 2024-06-06T14:01:22.513Z
83+
line = `{"timestamp":"1900-01-01T01:01:01.001Z","moduleId":"","ecid":"","message":""}`
84+
}
85+
86+
file.Close()
87+
88+
// read the timestamp from the line
89+
var lastLogRecord LogRecord
90+
err = json.Unmarshal([]byte(line), &lastLogRecord)
91+
if err != nil {
92+
level.Error(logger).Log("msg", "Could not parse last line of log file")
93+
return
94+
}
95+
96+
// query for any new alert log entries
97+
stmt := fmt.Sprintf(`select originating_timestamp, module_id, execution_context_id, message_text
98+
from v$diag_alert_ext
99+
where originating_timestamp > to_utc_timestamp_tz('%s')`, lastLogRecord.Timestamp)
100+
101+
rows, err := db.Query(stmt)
102+
if err != nil {
103+
level.Error(logger).Log("msg", "Error querying the alert logs")
104+
return
105+
}
106+
defer rows.Close()
107+
108+
// write them to the file
109+
outfile, err := os.OpenFile(logDestination, os.O_APPEND|os.O_WRONLY, 0600)
110+
if err != nil {
111+
level.Error(logger).Log("msg", "Could not open log file for writing: "+logDestination)
112+
return
113+
}
114+
115+
defer outfile.Close()
116+
117+
for rows.Next() {
118+
var newRecord LogRecord
119+
if err := rows.Scan(&newRecord.Timestamp, &newRecord.ModuleId, &newRecord.ECID, &newRecord.Message); err != nil {
120+
level.Error(logger).Log("msg", "Error reading a row from the alert logs")
121+
return
122+
}
123+
124+
// strip the newline from end of message
125+
newRecord.Message = strings.TrimSuffix(newRecord.Message, "\n")
126+
127+
jsonLogRecord, err := json.Marshal(newRecord)
128+
if err != nil {
129+
level.Error(logger).Log("msg", "Error marshalling alert log record")
130+
return
131+
}
132+
133+
if _, err = outfile.WriteString(string(jsonLogRecord) + "\n"); err != nil {
134+
level.Error(logger).Log("msg", "Could not write to log file: "+logDestination)
135+
return
136+
}
137+
}
138+
139+
if err = rows.Err(); err != nil {
140+
level.Error(logger).Log("msg", "Error querying the alert logs")
141+
}
142+
}

‎collector/collector.go

+54-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2021, 2023, Oracle and/or its affiliates.
1+
// Copyright (c) 2021, 2024, Oracle and/or its affiliates.
22
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
33
// Portions Copyright (c) 2016 Seth Miller <seth@sethmiller.me>
44

@@ -40,6 +40,8 @@ type Exporter struct {
4040
scrapeErrors *prometheus.CounterVec
4141
scrapeResults []prometheus.Metric
4242
up prometheus.Gauge
43+
dbtype int
44+
dbtypeGauge prometheus.Gauge
4345
db *sql.DB
4446
logger log.Logger
4547
}
@@ -49,6 +51,7 @@ type Config struct {
4951
User string
5052
Password string
5153
ConnectString string
54+
DbRole string
5255
MaxIdleConns int
5356
MaxOpenConns int
5457
CustomMetrics string
@@ -137,6 +140,11 @@ func NewExporter(logger log.Logger, cfg *Config) (*Exporter, error) {
137140
Name: "up",
138141
Help: "Whether the Oracle database server is up.",
139142
}),
143+
dbtypeGauge: prometheus.NewGauge(prometheus.GaugeOpts{
144+
Namespace: namespace,
145+
Name: "dbtype",
146+
Help: "Type of database the exporter is connected to (0=non-CDB, 1=CDB, >1=PDB).",
147+
}),
140148
logger: logger,
141149
config: cfg,
142150
}
@@ -196,6 +204,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
196204
ch <- e.error
197205
e.scrapeErrors.Collect(ch)
198206
ch <- e.up
207+
ch <- e.dbtypeGauge
199208
}
200209

201210
// RunScheduledScrapes is only relevant for users of this package that want to set the scrape on a timer
@@ -277,6 +286,8 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
277286
return
278287
}
279288

289+
e.dbtypeGauge.Set(float64(e.dbtype))
290+
280291
level.Debug(e.logger).Log("msg", "Successfully pinged Oracle database: "+maskDsn(e.connectString))
281292
e.up.Set(1)
282293

@@ -325,11 +336,15 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
325336

326337
scrapeStart := time.Now()
327338
if err = e.ScrapeMetric(e.db, ch, metric); err != nil {
328-
level.Error(e.logger).Log("msg", "Error scraping metric",
329-
"Context", metric.Context,
330-
"MetricsDesc", fmt.Sprint(metric.MetricsDesc),
331-
"time", time.Since(scrapeStart),
332-
"error", err)
339+
if !metric.IgnoreZeroResult {
340+
// do not print repetitive error messages for metrics
341+
// with ignoreZeroResult set to true
342+
level.Error(e.logger).Log("msg", "Error scraping metric",
343+
"Context", metric.Context,
344+
"MetricsDesc", fmt.Sprint(metric.MetricsDesc),
345+
"time", time.Since(scrapeStart),
346+
"error", err)
347+
}
333348
e.scrapeErrors.WithLabelValues(metric.Context).Inc()
334349
} else {
335350
level.Debug(e.logger).Log("msg", "Successfully scraped metric",
@@ -348,6 +363,14 @@ func (e *Exporter) connect() error {
348363
var P godror.ConnectionParams
349364
P.Username, P.Password, P.ConnectString = e.user, godror.NewPassword(e.password), e.connectString
350365

366+
if strings.ToUpper(e.config.DbRole) == "SYSDBA" {
367+
P.IsSysDBA = true
368+
}
369+
370+
if strings.ToUpper(e.config.DbRole) == "SYSOPER" {
371+
P.IsSysOper = true
372+
}
373+
351374
level.Debug(e.logger).Log("msg", "connection properties: "+fmt.Sprint(P))
352375

353376
// note that this just configures the connection, it does not acutally connect until later
@@ -360,9 +383,34 @@ func (e *Exporter) connect() error {
360383
db.SetConnMaxLifetime(0)
361384
level.Debug(e.logger).Log("msg", "Successfully configured connection to "+maskDsn(e.connectString))
362385
e.db = db
386+
387+
if _, err := db.Exec(`
388+
begin
389+
dbms_application_info.set_client_info('oracledb_exporter');
390+
end;`); err != nil {
391+
level.Info(e.logger).Log("msg", "Could not set CLIENT_INFO.")
392+
}
393+
394+
var result int
395+
if err := db.QueryRow("select sys_context('USERENV', 'CON_ID') from dual").Scan(&result); err != nil {
396+
level.Info(e.logger).Log("msg", "dbtype err ="+string(err.Error()))
397+
}
398+
e.dbtype = result
399+
400+
var sysdba string
401+
if err := db.QueryRow("select sys_context('USERENV', 'ISDBA') from dual").Scan(&sysdba); err != nil {
402+
level.Info(e.logger).Log("msg", "got error checking my database role")
403+
}
404+
level.Info(e.logger).Log("msg", "Connected as SYSDBA? "+sysdba)
405+
363406
return nil
364407
}
365408

409+
// this is used by the log exporter to share the database connection
410+
func (e *Exporter) GetDB() *sql.DB {
411+
return e.db
412+
}
413+
366414
func (e *Exporter) checkIfMetricsChanged() bool {
367415
for i, _customMetrics := range strings.Split(e.config.CustomMetrics, ",") {
368416
if len(_customMetrics) == 0 {

0 commit comments

Comments
 (0)
Failed to load comments.