Build an internal error notification system in Go and Svelte

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:

Comparison between Notify and Sentry

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.

Prerequisites for this tutorial

To follow along in this tutorial, you’ll need to install the following:

Setting up our project

In order to harness the power of Notify, we will be building a simple internal error notification system, which will be able to:

  • Log errors and send notifications to email and Slack
  • Display a dashboard that shows a list of notification logs

The project will be implemented in the following stack/frameworks:

Setting up the backend and services

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("john.doe@gmail.com", "<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.

Adding services to 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("john.doe@gmail.com")
notify.UseServices(email)

Sending notifications

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

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))
    }()
}

Using Svelte and Tailwind CSS for the frontend

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:

Setting up the test environment

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("https://247webdevs.blogspot.com/", 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:

Error Page That Needs Reporting

Once the error has been reported by the system, the custom-built dashboard displays the corresponding logs:

Custom Built Dashboard Displays 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):

Slack Preview

Email Preview

Conclusion

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.

The post Build an internal error notification system in Go and Svelte appeared first on LogRocket Blog.

from LogRocket Blog https://ift.tt/DilgTcx
Gain $200 in a week
via Read more


Source link