User-friendly Go library for building Grafana dashboards

Grabana

CI codecov GoDoc

Grabana provides a developer-friendly way of creating Grafana dashboards.

Whether you prefer writing code or YAML, if you are looking for a way to version your dashboards configuration or automate tedious and error-prone creation of dashboards, this library is meant for you.

Design goals

  • provide an understandable abstraction over dashboards configuration
  • expose a developer-friendly API
  • allow IDE assistance and auto-completion

Dashboard as code

Dashboard configuration:

builder := dashboard.New(
    "Awesome dashboard",
    dashboard.AutoRefresh("5s"),
    dashboard.Tags([]string{"generated"}),
    dashboard.VariableAsInterval(
        "interval",
        interval.Values([]string{"30s", "1m", "5m", "10m", "30m", "1h", "6h", "12h"}),
    ),
    dashboard.Row(
        "Prometheus",
        row.WithGraph(
            "HTTP Rate",
            graph.DataSource("prometheus-default"),
            graph.WithPrometheusTarget(
                "rate(prometheus_http_requests_total[30s])",
                prometheus.Legend("{{handler}} - {{ code }}"),
            ),
        ),
    ),
)

Dashboard creation:

ctx := context.Background()
client := grabana.NewClient(&http.Client{}, grafanaHost, grabana.WithAPIToken("such secret, much wow"))

// create the folder holding the dashboard for the service
folder, err := client.FindOrCreateFolder(ctx, "Test Folder")
if err != nil {
    fmt.Printf("Could not find or create folder: %s\n", err)
    os.Exit(1)
}

if _, err := client.UpsertDashboard(ctx, folder, builder); err != nil {
    fmt.Printf("Could not create dashboard: %s\n", err)
    os.Exit(1)
}

For a more complete example, see the example directory.

Dashboard as YAML

Dashboard configuration:

# dashboard.yaml
title: Awesome dashboard

editable: true
tags: [generated]
auto_refresh: 5s

variables:
  - interval:
      name: interval
      label: Interval
      values: ["30s", "1m", "5m", "10m", "30m", "1h", "6h", "12h"]

rows:
  - name: Prometheus
    panels:
      - graph:
          title: HTTP Rate
          height: 400px
          datasource: prometheus-default
          targets:
            - prometheus:
                query: "rate(promhttp_metric_handler_requests_total[$interval])"
                legend: "{{handler}} - {{ code }}"

Dashboard creation (or automatically as a Kubernetes Resource, using DARK):

content, err := ioutil.ReadFile("dashboard.yaml")
if err != nil {
    fmt.Fprintf(os.Stderr, "Could not read file: %s\n", err)
    os.Exit(1)
}

dashboard, err := decoder.UnmarshalYAML(bytes.NewBuffer(content))
if err != nil {
    fmt.Fprintf(os.Stderr, "Could not parse file: %s\n", err)
    os.Exit(1)
}

ctx := context.Background()
client := grabana.NewClient(&http.Client{}, grafanaHost, grabana.WithAPIToken("such secret, much wow"))

// create the folder holding the dashboard for the service
folder, err := client.FindOrCreateFolder(ctx, "Test Folder")
if err != nil {
    fmt.Printf("Could not find or create folder: %s\n", err)
    os.Exit(1)
}

if _, err := client.UpsertDashboard(ctx, folder, dashboard); err != nil {
    fmt.Printf("Could not create dashboard: %s\n", err)
    os.Exit(1)
}

Going further

Check out the documentation to discover what Grabana can do for you.

License

This library is under the MIT license.

Owner
Kévin Gomez
I can git now.
Kévin Gomez
Comments
  • Issues with upserting dashboard

    Issues with upserting dashboard

    https://grafana.com/docs/mimir/latest/operators-guide/reference-http-api/#rulerHi, all works like a charm, but I'm getting error when upserting dashboard. I'm not even using alerts, so maybe the functionality could be at least optional?

    error: could not prepare deletion of previous alerts for dashboard: could not query grafana: {"message":"Not found"} (HTTP status 404)
    

    If I try using cURL right on the particular query for rules:

    curl -H "Authorization: Bearer xxx" https://foo.bar/api/ruler/grafana/api/v1/rules?dashboard_uid=ygUo8se7k
    {"message":"Not found"}
    

    It looks like the alerts cleanup uses Mimir ruler API?

    I'm running self-hosted Grafana instance version 8.4.0 and using latest grabana v0.21.9

    Thanks for any pointers

  • Allow setting refresh on datasource variables

    Allow setting refresh on datasource variables

    This is a direct copy of the same feature on query variables. Not setting refresh on a datasource variable that uses another variable results in a dashboard that fails to load unless you "re-select" the variable in question.

  • Add support for basic authentication

    Add support for basic authentication

    In some deployments of Grafana, such as at my work (Sourcegraph) basic authentication is more sane than API tokens in e.g. development environments, since it can be automatically provisioned easily.

    This adds support for optionally making use of basic auth via a new NewClientBasicAuth method.

  • Could not prepare deletion of previous alerts for dashboard: invalid character '<' looking for beginning of value

    Could not prepare deletion of previous alerts for dashboard: invalid character '<' looking for beginning of value

    Hi all,

    I tried following the example code but got the following error.

    [ykyuen@camus ci-grafana]$ go run main.go 
    Could not create dashboard: could not prepare deletion of previous alerts for dashboard: invalid character '<' looking for beginning of value
    exit status 1
    

    I tried on grafana version 7.3.1 and 6.7.1. Both results in the above error but the dashboard is created correctly.

    The following is my main.go.

    package main
    
    import (
    	"context"
    	"fmt"
    	"net/http"
    	"os"
    
    	"github.com/K-Phoen/grabana"
    	"github.com/K-Phoen/grabana/dashboard"
    	"github.com/K-Phoen/grabana/row"
    	"github.com/K-Phoen/grabana/graph"
    	"github.com/K-Phoen/grabana/target/prometheus"
    	"github.com/K-Phoen/grabana/variable/interval"
    )
    
    func main() {
    	const GRAFANA_URL = "http://localhost:3000"
    	const GRAFANA_API_KEY = "<GRAFANA_API_KEY>"
    
    	ctx := context.Background()
    	client := grabana.NewClient(&http.Client{}, GRAFANA_URL, grabana.WithAPIToken(GRAFANA_API_KEY))
    	// Create the folder holding the dashboard for the service
    	folder, err := client.FindOrCreateFolder(ctx, "Testing")
    	if err != nil {
    		fmt.Printf("Could not find or create folder: %s\n", err)
    		os.Exit(1)
    	}
    	builder, err := dashboard.New(
    		"Awesome dashboard",
    		dashboard.AutoRefresh("5s"),
    		dashboard.Tags([]string{"generated"}),
    		dashboard.VariableAsInterval(
    			"interval",
    			interval.Values([]string{"30s", "1m", "5m", "10m", "30m", "1h", "6h", "12h"}),
    		),
    		dashboard.Row(
    			"Prometheus",
    			row.WithGraph(
    				"HTTP Rate",
    				graph.DataSource("prometheus-default"),
    				graph.WithPrometheusTarget(
    					"rate(prometheus_http_requests_total[30s])",
    					prometheus.Legend("{{handler}} - {{ code }}"),
    				),
    			),
    		),
    	)
    	if err != nil {
    		fmt.Printf("Could not build dashboard: %s\n", err)
    		os.Exit(1)
    	}
    	dash, err := client.UpsertDashboard(ctx, folder, builder)
    	if err != nil {
    		fmt.Printf("Could not create dashboard: %s\n", err)
    		os.Exit(1)
    	}
    	fmt.Printf("The deed is done:\n%s\n", GRAFANA_URL + dash.URL)
    }
    

    Any idea on what i have missed? Thanks for working on this project.

    Regards, Kit

  • Increase limit on number of listed folders

    Increase limit on number of listed folders

    The default limit for results when calling /api/folders is 1000. This increases the limit for client GetFolderByTitle to match that.

    See https://grafana.com/docs/grafana/v7.5/http_api/folder/

    GetFolderByTitle will still produce unexpected results if there are more than 1000 folders.

  • fix: sort interval vars by duration

    fix: sort interval vars by duration

    In most cases, it's desirable to have an 'interval' variable dropdown sorted by the actual time durations, from lowest to highest rather than a string-based sort.

    I'm opting to use the prometheus/common model.ParseDuration function because this includes extended parsing for durations including days (30d), weeks (2w), and years (1y), whereas the stdlib ParseDuration function only supports durations as large as 'hours'.

    Before: image

    After: image

    Fixes #105

  • table.HideColumn() does not work as expected

    table.HideColumn() does not work as expected

    Hey @K-Phoen,

    I'm attempting to port some of my existing dashboards to be built using this grabana library and I'm running into an issue with tables.

    Expected Behaviour:

    • Using table.HideColumn("MyColumn") function, I should be able to hide a table column.

    Actual Behaviour:

    • The column is never hidden, because the auto-inserted /.*/ "string" rule takes precedence and my column style rule is never evaluated.

    Additional Details:

    image

    Code:

    row.WithTable("Faults",
    				table.DataSource("$datasource"),
    				table.HideColumn(`/Time/`),
    				table.WithPrometheusTarget(`solidfire_cluster_active_faults{sfcluster=~"$cluster"}`,
    					prometheus.Instant(),
    					prometheus.Format(prometheus.FormatTable),
    				),
    				table.Span(12),
    			),
    

    I should note that I'm currently using Grafana 6.5.1 and v0.13.0 of grabana.

    I suspect that this behaviour is happening because of the default rule that gets inserted by the New() function in the table package.

    // New creates a new table panel.
    func New(title string, options ...Option) *Table {
    	panel := &Table{Builder: sdk.NewTable(title)}
    	empty := ""
    
    	panel.Builder.IsNew = false
    	panel.Builder.TablePanel.Styles = []sdk.ColumnStyle{
    		{
    			Alias:   &empty,
    			Pattern: "/.*/",
    			Type:    "string",
    		},
    	}
    
    	for _, opt := range append(defaults(), options...) {
    		opt(panel)
    	}
    
    	return panel
    }
    

    While building the table with the /.*/ default column style matches what Grafana UI gives us 'out of the box', having no way to remove this top-level rule is blocking me from achieving hiding any column in my table.

    If there is a way to remove this tableColumnStyle rule that gets auto-inserted please let me know. If there is no way right now, does the library require additional methods to 'reset' the table columns (or similar functionality?).

  • dashboard: add support for setting default time range and timezone of dashboards

    dashboard: add support for setting default time range and timezone of dashboards

    This adds support for setting the default time range and timezone of dashboards.

    This is useful to e.g. ensure dashboards are all UTC in order to match system logs and other monitoring tools.

  • New alerts

    New alerts

    BC everywhere, heavy WIP

    Todo:

    • [x] unit tests for stat panel
    • [x] unit tests for alerts
    • [x] unit tests for client
    • [x] stat panel YAML decoding
    • [x] unit tests for stat panel decoding
  • [Add] GetDashboardByTitle

    [Add] GetDashboardByTitle

    In this pull request I did this:

    • Implemented GetDashboardByTitle. Its functionality is very similar to that of GetFolderByTitle
    • Added more fields to the Dashboard struct for the API to return

    You can try it out by doing this:

    db, err := client.GetDashboardByTitle(ctx, "Awesome Dashboard")
    
    if err != nil {
    	fmt.Printf("Could not find dashboard: %s\n", err)
    	os.Exit(1)
    }
    
    fmt.Println(db)
    

    The above query returns *Dashboard and error

  • Add The Ability To Query For Dashboards by Title

    Add The Ability To Query For Dashboards by Title

    Using the client we are able to find if a folder exists. Likewise, It would be great if we could query whether a dashboard already exists.

    It could be accomplished with two functions like this:

    // This function queries the /api/search endpoint and returns the dashboard if it exists
    func (client *Client) getDashboardByTitle(ctx context.Context, title string) (*DashboardQuery, error) {
    	// 1. make a request to search for the dash board
    	query := fmt.Sprintf("/api/search?query=%s", title)
    	resp, err := client.get(ctx, query)
    
    	if err != nil {
    		err1 := fmt.Errorf("grafput: getDashboardByTitle %s", err)
    		return nil, err1
    	}
    
    	// 2. check the status code
    	if resp.StatusCode != http.StatusOK {
    		body, err := ioutil.ReadAll(resp.Body)
    		if err != nil {
    			err1 := fmt.Errorf("grafput: getDashboardByTitle: %s", err)
    			return nil, err1
    		}
    
    		return nil, fmt.Errorf("could not read body after querying dashboard: %s (HTTP status %d)", body, resp.StatusCode)
    	}
    
    	defer func() { _ = resp.Body.Close() }()
    
    	// 3. decode json using our Dashboard Query struct
    	var dashboard []DashboardQuery
    	if err := decodeJSON(resp.Body, &dashboardQuery); err != nil {
    		err1 := fmt.Errorf("grafput: getDashboardByTitle: %s", err)
    		return nil, err1
    	}
    
    	if len(dashboardQuery) == 0 {
    		return nil, nil
    	}
    
    	// 4. iterate through the list of dashboards until we match title
    	for i := range dashboardQuery {
    		if strings.EqualFold(dashboardQuery[i].Title, title) {
    			return &dashboardQuery[i], nil
    		}
    	}
    
    	return nil, nil
    }
    
    // This function enables us to search Grafana for a dashboard by its title
    func (client *Client) CheckIfDashboardExists(ctx context.Context, title string) (string, error) {
    	dashboard, err := client.getDashboardByTitle(ctx, title)
    	if err != nil {
    		err1 := fmt.Errorf("grafput: CheckIfDashboard Exists: %s", err)
    		return "", err1
    	}
    
    	if dashboard == nil {
    		return "", nil
    	}
    
    	dashboardUrl := dashboard.Url
    
           // here we could return the dashboard object or the URL 
    	return dashboardUrl, nil
    }
    
    type DashboardQuery struct {
    	Id          int      `json:"id"`
    	Uid         string   `json:"uid"`
    	Title       string   `json:"title"`
    	Uri         string   `json:"uri"`
    	Url         string   `json:"url"`
    	Slug        string   `json:"slug"`
    	Type        string   `json:"type"`
    	Tags        []string `json:"tags"`
    	IsStarred   bool     `json:"isStarred"`
    	FolderId    int      `json:"folderId"`
    	FolderUid   string   `json:"folderUid"`
    	FolderTitle string   `json:"folderTitle"`
    	FolderUrl   string   `json:"folderUrl"`
    	SortMeta    int      `json:"sortMeta"`
    }
    
  • feat: add NewFromBoard func to dahsboard builder

    feat: add NewFromBoard func to dahsboard builder

    Signed-off-by: Martin Chodur [email protected]

    Hi, this would make managing dashboards possible with the grabana client without necessarily using the grabana builder API (which does not support all properties of Grafana dashboard JSON) and create/manage also dashboards created using the sdk directly WDYT?

  • Support gridPos

    Support gridPos

    Hi, I see there is no support for gridPos for panels, this is not even in the SDK. Only the old span parameter.

    Interesting is that the grafana-tools/sdk supports this, I'm not sure how much is your fork of the SDK different from the original repo, but in some things it looks like the original one is a bit more up to date with the upstream dashboard JSON model. Is there any particular reason that the fork is used? :thinking:

  • Support stacking in timeseries panel

    Support stacking in timeseries panel

    I see the sdk supports the stacking configuration but unfortunately it's not possible to set it using Grabana

    It would be great if this would be possible.

  • Support `calculate` field for heatmap

    Support `calculate` field for heatmap

    Hi, in newer versions of Grafana if you want to use the Heatmap from Prometheus histogram you need to set the heatmap to "calculate": true, otherwise it does not work.

    Could it be added to the heatmap options? :pray:

A simple http service that generates *.PDF reports from Grafana dashboards.
A simple http service that generates *.PDF reports from Grafana dashboards.

Grafana reporter A simple http service that generates *.PDF reports from Grafana dashboards. Requirements Runtime requirements pdflatex installed and

Dec 27, 2022
An example logging system using Prometheus, Loki, and Grafana.
An example logging system using Prometheus, Loki, and Grafana.

Logging Example Structure Collector Export numerical data for Prometheus and log data for Promtail. Exporter uses port 8080 Log files are saved to ./c

Nov 21, 2022
Tiny structured logging abstraction or facade for various logging libraries, allowing the end user to plug in the desired logging library in main.go
Tiny structured logging abstraction or facade for various logging libraries, allowing the end user to plug in the desired logging library in main.go

Tiny structured logging abstraction or facade for various logging libraries, allowing the end user to plug in the desired logging library in main.go.

Dec 7, 2022
Gom: DOM building using Go

gom DOM building using Go Usage package main import "github.com/hadihammurabi/gom" func main() { dom := gom.H("html").Children( gom.H("head").Chi

Dec 16, 2021
ProfileStatusSyncer - A tool to synchronize user profile status of Github and Netease CloudMusic

ProfileStatusSyncer A tool to synchronize user profile status of GitHub and Nete

Jul 20, 2022
a lightweight, high-performance, out-of-the-box logging library that relies solely on the Go standard library

English | 中文 olog olog is a lightweight, high-performance, out-of-the-box logging library that relies solely on the Go standard library. Support outpu

Apr 12, 2023
Simple and blazing fast lockfree logging library for golang
Simple and blazing fast lockfree logging library for golang

glg is simple golang logging library Requirement Go 1.11 Installation go get github.com/kpango/glg Example package main import ( "net/http" "time"

Nov 28, 2022
Logging library for Golang

GLO Logging library for Golang Inspired by Monolog for PHP, severity levels are identical Install go get github.com/lajosbencz/glo Severity levels Deb

Sep 26, 2022
The Simplest and worst logging library ever written

gologger A Simple Easy to use go logger library. Displays Colored log into console in any unix or windows platform. You can even store your logs in fi

Sep 26, 2022
Gomol is a library for structured, multiple-output logging for Go with extensible logging outputs

gomol Gomol (Go Multi-Output Logger) is an MIT-licensed structured logging library for Go. Gomol grew from a desire to have a structured logging libra

Sep 26, 2022
Minimalistic logging library for Go.
Minimalistic logging library for Go.

logger Minimalistic logging library for Go. Blog Post Features: Advanced output filters (package and/or level) Attributes Timers for measuring perform

Nov 16, 2022
Seelog is a native Go logging library that provides flexible asynchronous dispatching, filtering, and formatting.

Seelog Seelog is a powerful and easy-to-learn logging framework that provides functionality for flexible dispatching, filtering, and formatting log me

Jan 3, 2023
A pure Go contextual logging library with "batteries included"

Cue Overview Cue implements contextual logging with "batteries included". It has thorough test coverage and supports logging to stdout/stderr, file, s

Sep 16, 2019
Golang logging library
Golang logging library

Golang logging library Package logging implements a logging infrastructure for Go. Its output format is customizable and supports different logging ba

Dec 27, 2022
Library and program to parse and forward HAProxy logs

haminer Library and program to parse and forward HAProxy logs. Supported forwarder, Influxdb Requirements Go for building from source code git for dow

Aug 17, 2022
A flexible process data collection, metrics, monitoring, instrumentation, and tracing client library for Go
A flexible process data collection, metrics, monitoring, instrumentation, and tracing client library for Go

Package monkit is a flexible code instrumenting and data collection library. See documentation at https://godoc.org/gopkg.in/spacemonkeygo/monkit.v3 S

Dec 14, 2022
Hierarchical, leveled, and structured logging library for Go

spacelog Please see http://godoc.org/github.com/spacemonkeygo/spacelog for info License Copyright (C) 2014 Space Monkey, Inc. Licensed under the Apach

Apr 27, 2021
Go Library [DEPRECATED]

Tideland Go Library Description The Tideland Go Library contains a larger set of useful Google Go packages for different purposes. ATTENTION: The cell

Nov 15, 2022
OpenTelemetry log collection library

opentelemetry-log-collection Status This project was originally developed by observIQ under the name Stanza. It has been contributed to the OpenTeleme

Sep 15, 2022