An internal error notification system is a crucial component of any large-scale system. It helps developers identify and fix problems that may arise in the system by reporting the log via various means, such as email, Slack, etc. In this article, we will look at how to build a simple internal error notification system in Golang using the notify
library.
Notify is a simple Go library for sending notifications to various messaging services. It has support for Slack, email, Amazon SES, Amazon SNS, Bark, DingTalk, MailGrid, Discord, Reddit, Viber, Twilio, and so much more.
Jump ahead:
The notify
package provides a platform-independent interface for sending a message to one or more messaging services. This can be useful for triggering certain actions or implementing a notification system.
Sentry is a cloud-based error tracking platform that helps developers track and fix errors in their applications. It provides real-time error reporting, alerts, and insights to help developers identify and fix errors as quickly as possible. Sentry supports a wide range of languages and platforms.
The main difference between notify
and Sentry is that notify
is a package for sending notifications to one or more messaging services, while Sentry is a platform for tracking and fixing errors in applications. They serve different purposes and are not directly comparable but building a system around Notify gives it more power to be used as a tool for reporting errors on multiple messaging services.
To follow along in this tutorial, you’ll need to install the following:
In order to harness the power of Notify, we will be building a simple internal error notification system, which will be able to:
The project will be implemented in the following stack/frameworks:
We need to create a file called alert.go
to handle error logging, sending notifications, and API endpoints for the dashboard.
We’ll start off by setting up the environment and installing packages:
go mod init main
We’ll use go get
, a Golang package manager, to install Notify:
go get -u github.com/nikoksr/notify
Next, we need to set up our services. Setting up services involves configuring the package to send notifications to various external services, such as email, Slack, etc.
To send notifications via email, you will need to install a package:
go get github.com/jordan-wright/email
Then, configure an SMTP server and provide the package with the necessary credentials, such as the server address, port number, and login credentials:
email := NewMailService("[email protected]", "<host>:<port>") email.AddReceivers("<email>") email.AuthenticateSMTP("", "<username>", "<password>", "<host>")
To send notifications to Slack, you will need to create a webhook in Slack, which will provide you with a unique URL that we’ll need to configure the package. Alternatively, you can decide to use the Notify recommendation, which can be installed with this:
go get github.com/slack-go/slack
However, as of now, the package contains a bug. To address this issue, I have implemented a solution specifically for this project:
func (s Slack) Send(ctx context.Context, subject, message string) error { data := map[string]string{ "text": "```\n" + message + "\n```", } j, err := json.Marshal(data) if err != nil { log.Fatal(err) } url := "https://hooks.slack.com/services/..." resp, err := http.Post(url, "application/json", bytes.NewBuffer(j)) if err != nil { return err } var res map[string]interface{} json.NewDecoder(resp.Body).Decode(&res) return nil }
Check out this list of external services supported by Notify.
Before adding service(s) to Notify, each of the service libraries must be instantiated, which will require one or more API keys or some other settings. These services can be added using UseServices()
:
notify.UseService(slack, telegram, ...) slack := SlackService() slack.AddReceiver("CHANNEL_ID") notify.UseServices(slack, ...) email := emailService() email.AddReceivers("[email protected]") notify.UseServices(email)
After you’ve added your services, Notify will send messages to the external service(s) using the following:
err := notify.Send( context.Background(), "Demo App:: Attention required", // notification title message, ) if err != nil { log.Fatal(err.Error()) }
Logger
is a function that handles the delivery of error messages to various services. It also allows us to log the message to a file, which is subsequently served to our dashboard via an endpoint:
func (l *Logger) Log(message string) { services := map[string]notify.Notifier{ "email": mailService(), "slack": slackService(), } var wg sync.WaitGroup for _, v := range l.services { if _, ok := services[v]; !ok { continue } wg.Add(1) v := v go (func() { defer wg.Done() notify.UseServices(services[v]) err := notify.Send( context.Background(), "Demo App:: Attention required", message, ) if err != nil { log.Fatal(err.Error()) } now := time.Now() logToFile(&LocalLog{ ServiceName: v, Date: now.Format(time.RFC3339), Environment: "production", // staging, dev, etc. Message: message, }) })() } wg.Wait() } func logToFile(payload *LocalLog) { f, err := os.OpenFile("local.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err) } res := fmt.Sprintf("[%v]::[%s]::[%s]:: %s", payload.ServiceName, payload.Environment, payload.Date, payload.Message) _, err = f.WriteString(res + "\n") if err != nil { log.Fatal(err) } f.Close() }
The endpoint is a part of the alert.go
file, which requires a server. To ensure it runs concurrently with our test environment server, we’ve included it in a goroutine.
The code below demonstrates how the endpoint handler reads streams of data from the log file. Note that the file logger can be replaced with other forms of a database, such as MongoDB, Firebase, etc:
func (a *Alert) Server(port string) { e := echo.New() UseCors(e) e.GET("/logs", handleLogs) go func() { e.Logger.Fatal(e.Start(port)) }() }
To set up Svelte for the frontend, we recommend following the instructions on the Svelte website. Svelte is a powerful and efficient JavaScript framework that enables developers to build high-performing web applications.
To spice things up, we’ll be using Svelte in conjunction with Tailwind, a popular utility-first CSS framework, to create a simple and responsive dashboard. Tailwind allows for rapid development and prototyping by providing pre-designed CSS classes that can be easily applied to our components:
<script lang="ts"> import TableList from "./components/TableList.svelte"; import Tailwindcss from "./components/Tailwind.svelte"; import Layout from "./components/Layout.svelte"; import Modal from "./components/Modal.svelte"; import Footer from "./components/Footer.svelte"; </script> <main> <Tailwindcss /> <Modal /> <Layout> <TableList /> </Layout> <Footer /> </main> <table class="w-full border-collapse bg-white text-left text-sm text-gray-500" > <thead class="bg-gray-50"> <tr> <th scope="col" class="px-6 py-4 font-medium text-gray-900">Service</th> <th scope="col" class="px-6 py-4 font-medium text-gray-900" >Environment</th > <!-- <th scope="col" class="px-6 py-4 font-medium text-gray-900">Status</th> --> <th scope="col" class="px-6 py-4 font-medium text-gray-900">Message</th> <th scope="col" class="px-6 py-4 font-medium text-gray-900">Date</th> <th scope="col" class="px-6 py-4 font-medium text-gray-900" /> </tr> </thead> <tbody class="divide-y divide-gray-100 border-t border-gray-100"> {#each $logs as log} <TableRow payload={log} /> {/each} </tbody> </table>
Here’s the simple dashboard UI that lists the errors reported from our test environment server:
To test our project and its integration, we need to implement a test environment.
A page is created and linked to alert.Log()
, which is also added to the Echo library HTTPErrorHandler
. This lets us capture and log all the page errors, which are then sent as a message to available message services:
// integration var alert = NewAlert() func main() { alert.Server(":1323") alert.SetNotificationService("email", "slack", "telegram") e := echo.New() e.HTTPErrorHandler = CustomHTTPErrorHandler e.GET("/", func(c echo.Context) error { alert.Log(fmt.Sprintf("LOG:[%v] error occured here this is a sample for error logger", t)) return c.String(http.StatusOK, "alert-notify is integerated with the endpoint !") }) e.Logger.Fatal(e.Start(":9000")) } func CustomHTTPErrorHandler(err error, c echo.Context) { if er, ok := err.(*echo.HTTPError); ok { alert.Log(string(er.Error())) c.Logger().Error(err) } }
The image below displays a page that couldn’t be found, resulting in an error that needs to be reported to our system using Notify. To accomplish this, we use Echo’s CustomHTTPErrorHandler(err error, c echo.Context)
function, which captures HTTP errors:
Once the error has been reported by the system, the custom-built dashboard displays the corresponding logs:
If the external service(s) is configured appropriately, we should see that our message is being delivered 🎉.
Here’s a preview of Slack and email (Mailtrap):
Building an internal error notification system using Go can provide a powerful and efficient solution for monitoring and addressing errors within your application. Go’s ability to handle concurrency and high-performance networking alongside with Notify makes it a great choice for the backend of the system. With this system in place, your development team will be able to quickly and effectively identify and resolve errors, improving the overall stability and reliability of your application.
Check out the GitHub repository for this project.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.