tutorial

How to Monitor Makeup with Vigilmon

Integrate Makeup syntax highlighting into your Elixir documentation pipeline and use Vigilmon to alert your team when highlighting breaks or lexers go missing.

How to Monitor Makeup with Vigilmon

Makeup is the syntax highlighter powering ExDoc. Where ExDoc transforms your @doc annotations into HTML, Makeup transforms code blocks inside those annotations into color-coded, token-classified output with full support for Elixir, Erlang, HTML, CSS, JavaScript, and more. Every code example on hexdocs.pm is highlighted by Makeup.

Makeup works through a lexer system: each language has a dedicated lexer package (makeup_elixir, makeup_erlang, makeup_js, etc.) that tokenizes source code before a formatter applies HTML or terminal color codes. When a lexer is missing, Makeup falls back to plain text — silently. Your docs ship without syntax highlighting and nobody notices until a user reports that code examples look wrong.

Vigilmon heartbeat monitors ensure your documentation pipeline — including syntax highlighting — stays functional.


Why Monitor Makeup?

Makeup failures are invisible in CI by default:

  • Missing lexers — a new language added to doc examples without adding the corresponding makeup_* lexer dep causes that language to render as plain text
  • Version conflicts — ExDoc and Makeup use a shared lexer registry; version mismatches cause some languages to fall back silently
  • Broken formatter output — custom formatters or themes that produce malformed HTML pass ExDoc's build step but break rendering in browsers
  • Lexer registration gapsMakeup.Registry not updated after adding a new lexer package causes lookup failures at render time
  • Performance regression — large code blocks in docs tokenized by a slow or quadratic lexer path cause doc generation to time out in CI

Key Metrics to Track

| Metric | What it tells you | |--------|-------------------| | Doc build CI pass rate | Whether ExDoc + Makeup runs to completion | | Highlighted language count | Whether all expected lexers are registered | | Doc generation duration | Regression indicator for lexer performance issues | | Plain-text fallback count | Number of code blocks rendered without syntax highlighting | | Time since last successful build | Whether docs pipeline was silently broken |


Step 1: Add Makeup and Its Lexers

# mix.exs
defp deps do
  [
    {:ex_doc, "~> 0.34", only: :dev, runtime: false},
    # Makeup is a transitive dependency of ex_doc, but pin it for control
    {:makeup, "~> 1.2", only: :dev, runtime: false},
    # Add lexers for languages you use in code examples
    {:makeup_elixir, "~> 0.16", only: :dev, runtime: false},
    {:makeup_erlang, "~> 1.0", only: :dev, runtime: false},
    {:makeup_js, "~> 0.5", only: :dev, runtime: false},
    {:makeup_html, "~> 0.1", only: :dev, runtime: false},
    # rest of your deps
  ]
end
mix deps.get

Verify the lexer registry after installing:

# In iex -S mix
Makeup.Registry.all_supported_languages()
# => ["elixir", "erlang", "javascript", "html", ...]

Step 2: Verify Lexer Registration at Build Time

Add a pre-doc build check that verifies all expected languages are registered:

# lib/mix/tasks/check_makeup_lexers.ex
defmodule Mix.Tasks.CheckMakeupLexers do
  use Mix.Task

  @shortdoc "Verify Makeup has lexers registered for all languages used in docs"

  @required_languages ~w[elixir erlang javascript html css]

  def run(_args) do
    available = Makeup.Registry.all_supported_languages()

    missing = Enum.reject(@required_languages, fn lang ->
      Enum.member?(available, lang)
    end)

    if missing == [] do
      Mix.shell().info("All required Makeup lexers are registered: #{Enum.join(@required_languages, ", ")}")
    else
      Mix.shell().error("""
      Missing Makeup lexers for: #{Enum.join(missing, ", ")}

      Add the corresponding makeup_* package to your deps in mix.exs:
      #{Enum.map_join(missing, "\n", fn lang -> "  {:makeup_#{lang}, \"~> 1.0\", only: :dev, runtime: false}" end)}
      """)
      exit({:shutdown, 1})
    end
  end
end

Step 3: Run Makeup Verification in CI

# .github/workflows/docs.yml
name: Documentation

on:
  push:
    branches: [main]
  pull_request:

jobs:
  docs:
    name: Generate Docs with Makeup
    runs-on: ubuntu-latest
    env:
      VIGILMON_MAKEUP_HEARTBEAT_URL: ${{ secrets.VIGILMON_MAKEUP_HEARTBEAT_URL }}
    steps:
      - uses: actions/checkout@v4

      - uses: erlef/setup-beam@v1
        with:
          elixir-version: '1.16'
          otp-version: '26'

      - name: Cache deps
        uses: actions/cache@v3
        with:
          path: deps
          key: ${{ runner.os }}-deps-${{ hashFiles('mix.lock') }}

      - run: mix deps.get

      - name: Verify Makeup lexers
        run: mix check_makeup_lexers

      - name: Generate docs
        run: mix docs

      - name: Verify highlighted output
        run: |
          # Check that syntax highlighting classes are present in generated HTML
          if grep -r 'class="highlight"' doc/ | head -1 | grep -q 'highlight'; then
            echo "Syntax highlighting present in generated docs"
          else
            echo "Warning: no highlighted code blocks found — check Makeup lexer registration"
            exit 1
          fi

      - name: Ping Vigilmon heartbeat
        if: success()
        run: curl -fsS "$VIGILMON_MAKEUP_HEARTBEAT_URL"

Step 4: Create a Heartbeat Monitor in Vigilmon

  1. Sign in at vigilmon.online
  2. Click New Monitor → Heartbeat
  3. Name it Makeup Doc Build — main branch
  4. Set the expected interval to 25 hours — doc builds on every push to main
  5. Copy the URL and store it as VIGILMON_MAKEUP_HEARTBEAT_URL in your CI secrets

If Makeup lexers are missing or doc generation fails, no heartbeat is sent and Vigilmon alerts you.


Step 5: Custom Makeup Formatter for Your Application

If you use Makeup in your web application (not just for ExDoc), monitor the formatter directly:

# lib/my_app/highlighter.ex
defmodule MyApp.Highlighter do
  @doc """
  Highlights source code in the given language.
  Falls back to plain text if no lexer is available.
  """
  def highlight(code, language) when is_binary(code) and is_binary(language) do
    available = Makeup.Registry.all_supported_languages()

    if language in available do
      Makeup.highlight(code, lexer_options: [language: language])
    else
      # Log the fallback so you can detect it in monitoring
      require Logger
      Logger.warning("No Makeup lexer for language: #{language}. Rendering as plain text.")
      plain_wrap(code)
    end
  end

  defp plain_wrap(code) do
    "<pre><code>#{Phoenix.HTML.html_escape(code)}</code></pre>"
  end
end

Track the fallback log pattern in your observability stack:

# Alertmanager rule or Vigilmon log monitor (if using log-based monitoring)
# Alert when "No Makeup lexer for language" appears more than 5 times per hour

Step 6: Alerting

In Vigilmon, configure Notifications → New Channel:

Slack:

🔴 MISSED: Makeup Doc Build — main branch
Last successful doc build: 30 hours ago (expected every 25 hours)
Action: Check CI — possible missing Makeup lexer or ExDoc version conflict

Pagerduty for production applications using Makeup for live syntax highlighting — a broken highlighter serves malformed HTML to users.


Common Makeup Issues and Fixes

Language not highlighted (silent fallback):

# In a doc comment
@doc """
Example shell session:

```shell
mix test

"""


If `makeup_shell` isn't installed, this renders as plain text. The fix: add the lexer dep or use a supported language alias (`console`, `bash`).

**Custom lexer not in registry:**

```elixir
# After adding a custom lexer module, register it explicitly
Makeup.Registry.register_lexer(MyApp.CustomLexer, [
  names: ["mylang"],
  extensions: ["ml"]
])

Do this in an Application.start/2 callback or a Mix task that runs before doc generation.

HTML escaping in highlighted output:

Makeup produces safe HTML — do not double-escape. When embedding in Phoenix templates:

<%# WRONG — double-escapes the markup %>
<%= @highlighted_code %>

<%# CORRECT — mark as safe since Makeup already escaped user content %>
<%= Phoenix.HTML.raw(@highlighted_code) %>

What You've Built

| What | How | |------|-----| | Makeup lexer verification | Custom Mix task checking registered languages | | CI integration | mix docs with lexer validation step | | Syntax highlight verification | Grep check for .highlight classes in generated HTML | | Doc build monitoring | Vigilmon heartbeat — alerts when build misses | | Safe fallback | Logger warning when lexer unavailable in production | | Failure alerting | Slack notification channel |

Makeup ensures code examples in your Elixir docs are readable and correctly highlighted. Vigilmon ensures the pipeline that runs Makeup never silently stops working.


Next Steps

  • Add custom Makeup themes matching your brand for web application code highlighting
  • Track fallback log events to discover which languages need new lexer packages
  • Pin Makeup and ExDoc versions together to prevent transitive dep conflicts
  • Set up a separate heartbeat for nightly full doc builds including all extras and changelogs

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →