Skip to content

Commit 278860c

Browse files
Merge pull request #780 from MateSaary/srep-962-dt-logs-timerange
SREP-962: Add absolute timestamp support to fetching DT logs (--from and --to flags)
2 parents a22144d + 1226887 commit 278860c

File tree

8 files changed

+100
-20
lines changed

8 files changed

+100
-20
lines changed

cmd/cluster/context.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -515,18 +515,20 @@ func (o *contextOptions) generateContextData() (*contextData, []error) {
515515
data.DyntraceEnvURL = "Failed to fetch Dynatrace URL"
516516
}
517517
return
518-
} else {
519-
query, err := dynatrace.GetQuery(hcpCluster)
520-
if err != nil {
521-
errors = append(errors, fmt.Errorf("failed to build query for Dynatrace %v", err))
522-
}
523-
queryTxt := query.Build()
524-
data.DyntraceEnvURL = hcpCluster.DynatraceURL
525-
data.DyntraceLogsURL, err = dynatrace.GetLinkToWebConsole(hcpCluster.DynatraceURL, 10, queryTxt)
526-
if err != nil {
527-
errors = append(errors, fmt.Errorf("failed to get url: %v", err))
528-
}
529518
}
519+
query, err := dynatrace.GetQuery(hcpCluster, time.Time{}, time.Time{}, 1) // passing nil from/to values to use --since behaviour
520+
if err != nil {
521+
errors = append(errors, fmt.Errorf("failed to build query for Dynatrace %v", err))
522+
data.DyntraceEnvURL = fmt.Sprintf("Failed to build Dynatrace query: %v", err)
523+
return
524+
}
525+
queryTxt := query.Build()
526+
data.DyntraceEnvURL = hcpCluster.DynatraceURL
527+
data.DyntraceLogsURL, err = dynatrace.GetLinkToWebConsole(hcpCluster.DynatraceURL, "now()-10h", "now()", queryTxt)
528+
if err != nil {
529+
errors = append(errors, fmt.Errorf("failed to get url: %v", err))
530+
}
531+
530532
}
531533

532534
GetPagerDutyAlerts := func() {

cmd/dynatrace/dtquery.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package dynatrace
33
import (
44
"fmt"
55
"strings"
6+
"time"
67
)
78

9+
const timeFormat = "2006-01-02T15:04:05Z"
10+
811
type DTQuery struct {
912
fragments []string
1013
finalQuery string
@@ -18,6 +21,17 @@ func (q *DTQuery) InitLogs(hours int) *DTQuery {
1821
return q
1922
}
2023

24+
func (q *DTQuery) InitLogsWithTimeRange(from time.Time, to time.Time) *DTQuery {
25+
q.fragments = []string{}
26+
27+
fromStr := from.Format(timeFormat)
28+
toStr := to.Format(timeFormat)
29+
30+
q.fragments = append(q.fragments, fmt.Sprintf("fetch logs, from:\"%s\", to:\"%s\" \n| filter matchesValue(event.type, \"LOG\") and ", fromStr, toStr))
31+
32+
return q
33+
}
34+
2135
func (q *DTQuery) InitEvents(hours int) *DTQuery {
2236
q.fragments = []string{}
2337

cmd/dynatrace/dtquery_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dynatrace
22

33
import (
44
"testing"
5+
"time"
56
)
67

78
func TestDTQuery_InitLogs(t *testing.T) {
@@ -192,3 +193,29 @@ func TestDTQuery_Deployments(t *testing.T) {
192193
})
193194
}
194195
}
196+
197+
func TestDTQuery_InitLogsWithTimeRange(t *testing.T) {
198+
tests := []struct {
199+
name string
200+
from time.Time
201+
to time.Time
202+
expected string
203+
}{
204+
{
205+
name: "Standard time range",
206+
from: time.Date(2025, 6, 12, 5, 0, 0, 0, time.UTC),
207+
to: time.Date(2025, 6, 17, 15, 0, 0, 0, time.UTC),
208+
expected: `fetch logs, from:"2025-06-12T05:00:00Z", to:"2025-06-17T15:00:00Z"
209+
| filter matchesValue(event.type, "LOG") and `,
210+
},
211+
}
212+
213+
for _, tt := range tests {
214+
t.Run(tt.name, func(t *testing.T) {
215+
q := new(DTQuery).InitLogsWithTimeRange(tt.from, tt.to)
216+
if q.fragments[0] != tt.expected {
217+
t.Errorf("expected: %s\ngot: %s", tt.expected, q.fragments[0])
218+
}
219+
})
220+
}
221+
}

cmd/dynatrace/logsCmd.go

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/url"
7+
"time"
78

89
k8s "github.com/openshift/osdctl/pkg/k8s"
910
"github.com/spf13/cobra"
@@ -14,6 +15,8 @@ var (
1415
dryRun bool
1516
tail int
1617
since int
18+
fromVar time.Time
19+
toVar time.Time
1720
contains string
1821
sortOrder string
1922
clusterID string
@@ -52,6 +55,9 @@ const (
5255
# Only return logs newer than 2 hours old (an integer in hours)
5356
$ osdctl dt logs alertmanager-main-0 -n openshift-monitoring --since 2
5457
58+
# Get logs for a specific time range using --from and --to flags
59+
$ osdctl dt logs alertmanager-main-0 -n openshift-monitoring --from "2025-06-15 04:00" --to "2025-06-17 13:00"
60+
5561
# Restrict return of logs to those that contain a specific phrase
5662
$ osdctl dt logs alertmanager-main-0 -n openshift-monitoring --contains <phrase>
5763
`
@@ -89,6 +95,11 @@ func NewCmdLogs() *cobra.Command {
8995
logsCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Only builds the query without fetching any logs from the tenant")
9096
logsCmd.Flags().IntVar(&tail, "tail", 1000, "Last 'n' logs to fetch (defaults to 100)")
9197
logsCmd.Flags().IntVar(&since, "since", 1, "Number of hours (integer) since which to search (defaults to 1 hour)")
98+
logsCmd.Flags().TimeVar(&fromVar, "from", time.Time{}, []string{time.RFC3339, "2006-01-02 15:04"}, "Datetime from which to filter logs, in the format \"YYYY-MM-DD HH:MM\"")
99+
logsCmd.Flags().TimeVar(&toVar, "to", time.Time{}, []string{time.RFC3339, "2006-01-02 15:04"}, "Datetime until which to filter logs to, in the format \"YYYY-MM-DD HH:MM\"")
100+
logsCmd.MarkFlagsRequiredTogether("from", "to")
101+
logsCmd.MarkFlagsMutuallyExclusive("since", "from")
102+
logsCmd.MarkFlagsMutuallyExclusive("since", "to")
92103
logsCmd.Flags().StringVar(&contains, "contains", "", "Include logs which contain a phrase")
93104
logsCmd.Flags().StringVar(&sortOrder, "sort", "asc", "Sort the results by timestamp in either ascending or descending order. Accepted values are 'asc' and 'desc'. Defaults to 'asc'")
94105
logsCmd.Flags().StringSliceVar(&nodeList, "node", []string{}, "Node name(s) (comma-separated)")
@@ -100,13 +111,13 @@ func NewCmdLogs() *cobra.Command {
100111
return logsCmd
101112
}
102113

103-
func GetLinkToWebConsole(dtURL string, since int, finalQuery string) (string, error) {
114+
func GetLinkToWebConsole(dtURL string, from string, to string, finalQuery string) (string, error) {
104115
SearchQuery := map[string]interface{}{
105116
"version": 1,
106117
"dt.query": finalQuery,
107118
"dt.timeframe": map[string]interface{}{
108-
"from": fmt.Sprintf("now()-%vh", since),
109-
"to": "now()",
119+
"from": from,
120+
"to": to,
110121
},
111122
"showDqlEditor": true,
112123
"tableConfig": map[string]interface{}{
@@ -132,6 +143,11 @@ func main(clusterID string) error {
132143
if since <= 0 {
133144
return fmt.Errorf("invalid time duration")
134145
}
146+
147+
if !fromVar.IsZero() && !toVar.IsZero() && toVar.Before(fromVar) {
148+
return fmt.Errorf("--to cannot be set to a datetime before --from")
149+
}
150+
135151
hcpCluster, err := FetchClusterDetails(clusterID)
136152
if err != nil {
137153
return fmt.Errorf("failed to acquire cluster details %v", err)
@@ -141,15 +157,23 @@ func main(clusterID string) error {
141157
return fmt.Errorf("invalid sort order, expecting 'asc' or 'desc'")
142158
}
143159

144-
query, err := GetQuery(hcpCluster)
160+
query, err := GetQuery(hcpCluster, fromVar, toVar, since)
145161
if err != nil {
146162
return fmt.Errorf("failed to build query for Dynatrace %v", err)
147163
}
148164

149165
fmt.Println(query.Build())
150166

151167
if console {
152-
url, err := GetLinkToWebConsole(hcpCluster.DynatraceURL, since, query.finalQuery)
168+
var url string
169+
var err error
170+
171+
if !fromVar.IsZero() && !toVar.IsZero() { // Absolute timestamp condition
172+
url, err = GetLinkToWebConsole(hcpCluster.DynatraceURL, fromVar.Format(time.RFC3339), toVar.Format(time.RFC3339), query.finalQuery)
173+
} else { // otherwise relative (since "mode")
174+
url, err = GetLinkToWebConsole(hcpCluster.DynatraceURL, fmt.Sprintf("now()-%dh", since), "now()", query.finalQuery)
175+
}
176+
153177
if err != nil {
154178
return fmt.Errorf("failed to get url: %v", err)
155179
}
@@ -179,9 +203,14 @@ func main(clusterID string) error {
179203
return nil
180204
}
181205

182-
func GetQuery(hcpCluster HCPCluster) (query DTQuery, error error) {
206+
func GetQuery(hcpCluster HCPCluster, fromVar time.Time, toVar time.Time, since int) (query DTQuery, error error) {
183207
q := DTQuery{}
184-
q.InitLogs(since).Cluster(hcpCluster.managementClusterName)
208+
209+
if !fromVar.IsZero() && !toVar.IsZero() {
210+
q.InitLogsWithTimeRange(fromVar, toVar).Cluster(hcpCluster.managementClusterName)
211+
} else {
212+
q.InitLogs(since).Cluster(hcpCluster.managementClusterName)
213+
}
185214

186215
if hcpCluster.hcpNamespace != "" {
187216
namespaceList = append(namespaceList, hcpCluster.hcpNamespace)

docs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,6 +2308,7 @@ osdctl dynatrace logs --cluster-id <cluster-identifier> [flags]
23082308
--contains string Include logs which contain a phrase
23092309
--context string The name of the kubeconfig context to use
23102310
--dry-run Only builds the query without fetching any logs from the tenant
2311+
--from time Datetime from which to filter logs, in the format "YYYY-MM-DD HH:MM" (default 0001-01-01T00:00:00Z)
23112312
-h, --help help for logs
23122313
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
23132314
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
@@ -2322,6 +2323,7 @@ osdctl dynatrace logs --cluster-id <cluster-identifier> [flags]
23222323
--sort string Sort the results by timestamp in either ascending or descending order. Accepted values are 'asc' and 'desc'. Defaults to 'asc' (default "asc")
23232324
--status strings Status(Info/Warn/Error) (comma-separated)
23242325
--tail int Last 'n' logs to fetch (defaults to 100) (default 1000)
2326+
--to time Datetime until which to filter logs to, in the format "YYYY-MM-DD HH:MM" (default 0001-01-01T00:00:00Z)
23252327
```
23262328

23272329
### osdctl dynatrace url

docs/osdctl_dynatrace_logs.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ osdctl dynatrace logs --cluster-id <cluster-identifier> [flags]
3737
# Only return logs newer than 2 hours old (an integer in hours)
3838
$ osdctl dt logs alertmanager-main-0 -n openshift-monitoring --since 2
3939
40+
# Get logs for a specific time range using --from and --to flags
41+
$ osdctl dt logs alertmanager-main-0 -n openshift-monitoring --from "2025-06-15 04:00" --to "2025-06-17 13:00"
42+
4043
# Restrict return of logs to those that contain a specific phrase
4144
$ osdctl dt logs alertmanager-main-0 -n openshift-monitoring --contains <phrase>
4245
@@ -50,13 +53,15 @@ osdctl dynatrace logs --cluster-id <cluster-identifier> [flags]
5053
--container strings Container name(s) (comma-separated)
5154
--contains string Include logs which contain a phrase
5255
--dry-run Only builds the query without fetching any logs from the tenant
56+
--from time Datetime from which to filter logs, in the format "YYYY-MM-DD HH:MM" (default 0001-01-01T00:00:00Z)
5357
-h, --help help for logs
5458
-n, --namespace strings Namespace(s) (comma-separated)
5559
--node strings Node name(s) (comma-separated)
5660
--since int Number of hours (integer) since which to search (defaults to 1 hour) (default 1)
5761
--sort string Sort the results by timestamp in either ascending or descending order. Accepted values are 'asc' and 'desc'. Defaults to 'asc' (default "asc")
5862
--status strings Status(Info/Warn/Error) (comma-separated)
5963
--tail int Last 'n' logs to fetch (defaults to 100) (default 1000)
64+
--to time Datetime until which to filter logs to, in the format "YYYY-MM-DD HH:MM" (default 0001-01-01T00:00:00Z)
6065
```
6166

6267
### Options inherited from parent commands

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ require (
5151
github.com/sirupsen/logrus v1.9.3
5252
github.com/spf13/afero v1.12.0
5353
github.com/spf13/cobra v1.9.1
54-
github.com/spf13/pflag v1.0.6
54+
github.com/spf13/pflag v1.0.7
5555
github.com/spf13/viper v1.20.1
5656
github.com/stretchr/testify v1.10.0
5757
github.com/zclconf/go-cty v1.13.0

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,8 +603,9 @@ github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cA
603603
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
604604
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
605605
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
606-
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
607606
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
607+
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
608+
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
608609
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
609610
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
610611
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=

0 commit comments

Comments
 (0)