🪟 Windows TippsModify Windows Support Phone Number with PowerShell(03.09.2026 um 00:00 Uhr)
🔧 AI Nachrichten Podcast: ChatGPT schwatzt Nutzern in Deutschland jetzt Werbung auf(28.08.2026 um 08:46 Uhr)
🪟 Windows TippsMicrosoft bringt Emoji 17.0 auf Windows 11(31.08.2026 um 08:16 Uhr)
🪟 Windows TippsModify Windows Support Phone Number with PowerShell(03.09.2026 um 00:00 Uhr)
🔧 AI Nachrichten Podcast: ChatGPT schwatzt Nutzern in Deutschland jetzt Werbung auf(28.08.2026 um 08:46 Uhr)
🪟 Windows TippsMicrosoft bringt Emoji 17.0 auf Windows 11(31.08.2026 um 08:16 Uhr)

🔧 Programmierung 🕛 vor 1 Jahr 15 Min Lesezeit
0

Mastering Golang Debugging in Emacs

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Introduction



Since I've started developing in Golang I didn't really use the debugger. Instead I was naively adding fmt.Print statements everywhere to validate my code 🙈. While print statements and logs might be also your first debugging instinct, they often fall short when dealing with large and complex code base, with sophisticated runtime behaviour and (of course!) complex concurrency issues that seem impossible to reproduce.



After starting working on more complex projects (like this one: , . Together, these tools create a debugging experience that mimics (and often surpasses) traditional IDEs, while preserving the flexibility and extensibility that Emacs is famous for.



This is what you can expect:




  • Set up and configure

  • Debug both standard applications and as a package manager, , as my LSP client.






    Required Emacs Packages



    For Emacs 29+ users, eglot is built-in. Check out . We'll first add dape:




    CODE
    (use-package dape
    :straight t
    :config
    ;; Pulse source line (performance hit)
    (add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line)

    ;; To not display info and/or buffers on startup
    ;; (remove-hook 'dape-start-hook 'dape-info)
    (remove-hook 'dape-start-hook 'dape-repl))






    And go-mode:




    CODE
    (use-package go-mode
    :straight t
    :mode "\\.go\\'"
    :hook ((before-save . gofmt-before-save))
    :bind (:map go-mode-map
    ("M-?" . godoc-at-point)
    ("M-." . xref-find-definitions)
    ("M-_" . xref-find-references)
    ;; ("M-*" . pop-tag-mark) ;; Jump back after godef-jump
    ("C-c m r" . go-run))
    :custom
    (gofmt-command "goimports"))









    Installing Required Go Tools



    Install Delve and gopls, the LSP server:




    CODE
    # Install Delve
    go install github.com/go-delve/delve/cmd/dlv@latest

    # Install gopls
    go install golang.org/x/tools/gopls@latest






    Additionally I have a bunch of other tools which I use from time to time:




    CODE
    go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
    go install github.com/onsi/ginkgo/v2/ginkgo@latest

    go install -v golang.org/x/tools/cmd/godoc@latest
    go install -v golang.org/x/tools/cmd/goimports@latest
    go install -v github.com/stamblerre/gocode@latest
    go install -v golang.org/x/tools/cmd/gorename@latest
    go install -v golang.org/x/tools/cmd/guru@latest
    go install -v github.com/cweill/gotests/...@latest

    go install -v github.com/davidrjenni/reftools/cmd/fillstruct@latest
    go install -v github.com/fatih/gomodifytags@latest
    go install -v github.com/godoctor/godoctor@latest
    go install -v github.com/haya14busa/gopkgs/cmd/gopkgs@latest
    go install -v github.com/josharian/impl@latest
    go install -v github.com/rogpeppe/godef@latest






    Then you need to configure the corresponding Emacs packages:




    CODE
    (use-package ginkgo
    :straight (:type git :host github :repo "garslo/ginkgo-mode")
    :init
    (setq ginkgo-use-pwd-as-test-dir t
    ginkgo-use-default-keys t))

    (use-package gotest
    :straight t
    :after go-mode
    :bind (:map go-mode-map
    ("C-c t f" . go-test-current-file)
    ("C-c t t" . go-test-current-test)
    ("C-c t j" . go-test-current-project)
    ("C-c t b" . go-test-current-benchmark)
    ("C-c t c" . go-test-current-coverage)
    ("C-c t x" . go-run)))

    (use-package go-guru
    :straight t
    :hook
    (go-mode . go-guru-hl-identifier-mode))

    (use-package go-projectile
    :straight t
    :after (projectile go-mode))

    (use-package flycheck-golangci-lint
    :straight t
    :hook
    (go-mode . flycheck-golangci-lint-setup))

    (use-package go-eldoc
    :straight t
    :hook
    (go-mode . go-eldoc-setup))

    (use-package go-tag
    :straight t
    :bind (:map go-mode-map
    ("C-c t a" . go-tag-add)
    ("C-c t r" . go-tag-remove))
    :init (setq go-tag-args (list "-transform" "camelcase")))

    (use-package go-fill-struct
    :straight t)

    (use-package go-impl
    :straight t)

    (use-package go-playground
    :straight t)









    Dape Configuration



    There is no particular reason why I use dape instead of it was part of it and I just got used to it. As the you can tweak in your debugging configuration:
































































    Property Description
    name Name for your configuration that appears in the drop down in the Debug viewlet
    type Always set to "go". This is used by VS Code to figure out which extension should be used for debugging your code
    request Either of launch or attach. Use attach when you want to attach to an already running process
    mode For launch requests, either of auto, debug, remote, test, exec. For attach requests, use either local or remote
    program Absolute path to the package or file to debug when in debug & test mode, or to the pre-built binary file to debug in exec mode
    env Environment variables to use when debugging. Example: { "ENVNAME": "ENVVALUE" }
    envFile Absolute path to a file containing environment variable definitions
    args Array of command line arguments that will be passed to the program being debugged
    showLog Boolean indicating if logs from delve should be printed in the debug console
    logOutput Comma separated list of delve components for debug output
    buildFlags Build flags to be passed to the Go compiler
    remotePath Absolute path to the file being debugged on the remote machine
    processId ID of the process that needs debugging (for attach request with local mode)





    Sample Application



    Now let's put our knowledge into practice by debugging a real application implementing a REST API.






    Project Structure



    Our example is a REST API for task management with the following structure:




    CODE
    taskapi/
    ├── go.mod
    ├── go.sum
    ├── main.go
    ├── task_store.go
    └── task_test.go









    Core Components



    Let's have a look at the core components.



    The Task represents our core domain model:




    CODE
    import (
    "fmt"
    )

    type Task struct {
    ID int `json:"id"`
    Title string `json:"title"`
    Description string `json:"description"`
    Done bool `json:"done"`
    }






    The TaskStore handles our in-memory data operations:




    CODE
    type TaskStore struct {
    tasks map[int]Task
    nextID int
    }

    func NewTaskStore() *TaskStore {
    return &TaskStore{
    tasks: make(map[int]Task),
    nextID: 1,
    }
    }









    REST API



    The API exposes following endpoints:





    • POST /task/create - Creates a new task


    • GET /task/get?id=<id> - Retrieves a task by ID




    CODE
    // CreateTask stores a given Task internally
    func (ts *TaskStore) CreateTask(task Task) Task {
    task.ID = ts.nextID
    ts.tasks[task.ID] = task
    ts.nextID++
    return task
    }

    // GetTask retrieves a Task by ID
    func (ts *TaskStore) GetTask(id int) (Task, error) {
    task, exists := ts.tasks[id]
    if !exists {
    return Task{}, fmt.Errorf("task with id %d not found", id)
    }
    return task, nil
    }

    // UpdateTask updates task ID with a new Task object
    func (ts *TaskStore) UpdateTask(id int, task Task) error {
    if _, exists := ts.tasks[id]; !exists {
    return fmt.Errorf("task with id %d not found", id)
    }
    task.ID = id
    ts.tasks[id] = task
    return nil
    }









    Server



    Here's the server implementation:




    CODE
    package main

    import (
    "encoding/json"
    "fmt"
    "net/http"
    )

    // Server implements a web application for managing tasks
    type Server struct {
    store *TaskStore
    }

    func (s *Server) handleCreateTask(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
    http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    return
    }
    var task Task
    if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
    }

    createdTask := s.store.CreateTask(task)
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(createdTask)
    }

    func (s *Server) handleGetTask(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
    http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    return
    }
    id := 0
    fmt.Sscanf(r.URL.Query().Get("id"), "%d", &id)

    task, err := s.store.GetTask(id)
    if err != nil {
    http.Error(w, err.Error(), http.StatusNotFound)
    return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(task)
    }






    Let's look at our main function:




    CODE
    package main

    import (
    "log"
    "net/http"
    )

    func main() {
    store := NewTaskStore()
    server := &Server{store: store}
    http.HandleFunc("/task/create", server.handleCreateTask)
    http.HandleFunc("/task/get", server.handleGetTask)

    log.Printf("Starting server on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
    }









    Build application



    Let's start the server:




    CODE
    go build -o taskapi *.go
    ./taskapi
    2024/11/14 07:03:48 Starting server on :8080






    Now from a different terminal create a new task:




    CODE
    curl -X POST -s http://localhost:8080/task/create \
    -H "Content-Type: application/json" \
    -d '{"title":"Learn Debugging","description":"Master Emacs debugging with dape","done":false}'






    Response:




    CODE
    {"id":3,"title":"Learn Debugging","description":"Master Emacs debugging with dape","done":false}






    Let's see if we can fetch it:




    CODE
    curl -X GET -s "http://localhost:8080/task/get?id=1"






    Response:




    CODE
    {"id":1,"title":"Learn Debugging","description":"Master Emacs debugging with dape","done":false}









    Unit tests



    Below are some unit tests (written in






    Basic Debugging with Delve and Dape



    In order to debug our Task API we have following approaches:




    • we can launch the application directly and debug it

    • we can attach to a running process

    • we can attach to a running debugging session



    Here are the options for different request types:
























































    request mode required optional
    launch debug program dlvCwd, env, backend, args, cwd, buildFlags, output, noDebug
    test program dlvCwd, env, backend, args, cwd, buildFlags, output, noDebug
    exec program dlvCwd, env, backend, args, cwd, noDebug
    core program, corefilePath dlvCwd, env
    replay traceDirPath dlvCwd, env
    attach local processId backend
    remote





    Profile 1: Launch application



    Here's our first debugging profile for .dir-locals.el:




    CODE
    ;; Profile 1: Launch application and start DAP server
    (go-debug-taskapi
    modes (go-mode go-ts-mode)
    command "dlv"
    command-args ("dap" "--listen" "127.0.0.1:55878")
    command-cwd default-directory
    host "127.0.0.1"
    port 55878
    :request "launch"
    :mode "debug"
    :type "go"
    :showLog "true"
    :program ".")






    💡 You may want to use a different value for command-cwd. In my case I wanted to start the debugger in a directory which currently is not a project. default-directory is a variable which holds the working directory for the current buffer you're currently in.



    Start debugging:




    • Run dape-info to show debugging information





    After starting the debugger with this profile, you should see in the dape-repl buffer:




    CODE
    Available Dape commands: debug, next, continue, pause, step, out, up, down, restart, kill, disconnect, quit
    Empty input will rerun last command.

    DAP server listening at: 127.0.0.1:55878
    debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-1600.0.36.3
    for arm64.
    Got a connection, launched process __debug_bin3666561508 (pid = 43984).
    Type 'dlv help' for list of commands.






    Note that we didn't specify any binary/file to debug (we had :program "." in .dir-locals.el). delve will automatically build the binary before it launches the application:




    CODE
    go build -gcflags=all="-N -l" .









    Profile 2: Attach to an external debugger



    Let's add a profile for connecting to an existing debugging session:




    CODE
    ;; Profile 2: Attach to external debugger
    (go-attach-taskapi
    modes (go-mode go-ts-mode)
    command "dlv"
    command-cwd default-directory
    host "127.0.0.1" ;; can also be skipped
    port 55878
    :request "attach" ;; this will run "dlv attach ..."
    :mode "remote" ;; connect to a running debugger session
    :type "go"
    :showLog "true")






    Now let's start the debugger on the CLI:




    CODE
    $ go build -gcflags=all="-N -l" -o taskapi .
    $ dlv debug taskapi --listen=localhost:55878 --headless
    API server listening at: 127.0.0.1:55878
    debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-1600.0.36.3
    for arm64.
    Got a connection, launched process __debug_bin794004190 (pid = 23979).






    Now within Emacs you can launch dape and select the go-attach-taskapi profile:





    Now I start the debugger:








    Debugging Ginkgo Tests



    Being able to debug tests in Golang is crucial. For running ginkgo tests I use





    Now the configuration is quite static and therefore you cannot preselect the unit test / container. We need to somehow make the parameter -ginkgo.focus dynamic:




    CODE
    (defun my/dape-debug-ginkgo-focus (focus-string)
    "Start debugging Ginkgo tests with a specific focus string."
    (interactive "sEnter focus string: ")
    (make-local-variable 'dape-configs) ; Make buffer-local copy of dape-configs
    (setq dape-configs
    (list
    `(debug-focused-test
    modes (go-mode)
    command "dlv"
    command-args ("dap" "--listen" "127.0.0.1:55878")
    command-cwd default-directory
    port 55878
    :request "launch"
    :name "Debug Focused Test"
    :mode "test"
    :program "."
    :args ["-ginkgo.v" "-ginkgo.focus" ,focus-string]))))









  • Ginkgo Testing Framework

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Modify Windows Support Phone Number with PowerShell
1 Quelle
Die Zukunft des Einkaufens: Warum wir ein neues Kapitel aufschlagen (und wie du es mitschreiben kannst)
1 Quelle
ZDE Podcast 251: Wie sieht digitales Instore Marketing 2026 aus, Amit Chatterjee?
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Mastering Golang Debugging in Emacs

Thematisch verwandte Begriffe: Mastering, Golang, Debugging, Emacs · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...