# JavaScript Logging & Error Tracking: Console, Errors, and Monitoring

Source: https://devdocs10.com/articles/javascript/js-logging-errors  
By Murat Bo · Published 2026-07-25 · DevDocs10 (https://devdocs10.com)

Logging and error tracking help you understand what your JavaScript code is doing, catch failures early, and diagnose problems after they happen. In the browser or on the server, good logging turns guesswork into a clear trail of events, warnings, and errors.

> **Quick answer:** Use `console` methods for local debugging, use `try...catch` and `throw` for recoverable failures, and use an error-tracking service or centralized logs to capture issues you cannot reproduce locally.

> **Difficulty:** Beginner
>
> **You'll understand this better if you know:** basic JavaScript syntax, functions, objects, and how promises and async code work at a simple level.

## 1. What Is JavaScript Logging & Error Tracking?

JavaScript logging is the practice of writing useful messages during program execution so you can see what happened and in what order. Error tracking is the process of capturing failures, stack traces, and context so you can find, prioritize, and fix bugs.

- `console.log()` helps you inspect values while developing.
- `console.warn()` and `console.error()` separate warnings from failures.
- `throw` lets you stop execution when something is invalid.
- `try...catch` handles failures without crashing the whole flow.
- Error tracking systems record exceptions, stack traces, and request or user context.

## 2. Why JavaScript Logging & Error Tracking Matters

Without logs, many bugs are invisible until users report them. With logs, you can see the sequence of events that led to a problem, which values were passed into a function, and where the code failed.

Good error tracking also helps teams in production. If a bug only appears on one device, one browser, or one data set, recorded errors make it possible to diagnose it after the fact.

## 3. Basic Syntax

Logging and error tracking usually start with two ideas: write information out, and detect when something goes wrong. The simplest tools are console methods and the built-in `Error` object.

### Basic logging

You can print values directly to the console to inspect state while the program runs.

```javascript
const userName = "Ava";
const cartItems = 3;

console.log("User:", userName);
console.log("Items in cart:", cartItems);
```

Output (tested with Node 24.21.0):

```text
User: Ava
Items in cart: 3
```

This example shows how simple messages can help you confirm that values exist and have the expected type.

### Basic error creation

When a function cannot continue safely, it can stop by throwing an error.

```javascript
function parseAge(value) {
  if (typeof value !== "number") {
    throw new Error("Age must be a number.");
  }

  return value;
}
```

The caller can then decide whether to handle that failure or let it bubble up.

## 4. Step-by-Step Examples

### Example 1: Tracing a function with console methods

Start with logs at the beginning, middle, and end of a function so you can see the control flow.

```javascript
function formatTotal(subtotal, taxRate) {
  console.log("formatTotal started", { subtotal, taxRate });

  const tax = subtotal * taxRate;
  console.debug("Calculated tax", tax);

  const total = subtotal + tax;
  console.log("formatTotal finished", total);

  return total;
}
```

This approach helps you locate whether the bug is in the input, the calculation, or the return value.

### Example 2: Catching a recoverable error

Use `try...catch` when you can show a fallback or display a useful message instead of crashing.

```javascript
function loadProfile(jsonText) {
  try {
    return JSON.parse(jsonText);
  } catch (error) {
    console.error("Invalid profile data:", error);
    return null;
  }
}
```

This pattern is common when parsing user input or data returned from an API.

### Example 3: Throwing a custom error for invalid input

When a caller gives you invalid arguments, fail early with a clear message.

```javascript
function setDiscount(percent) {
  if (percent < 0 || percent > 100) {
    throw new RangeError("Discount must be between 0 and 100.");
  }

  return percent;
}
```

Using a more specific error type makes the problem easier to classify and debug.

### Example 4: Logging an async failure

Async operations often fail later, so you need to handle both promise rejections and thrown errors.

```javascript
async function fetchUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);

    if (!response.ok) {
      throw new Error(`Request failed with status ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error("Failed to fetch user", error);
    throw error;
  }
}
```

This pattern logs the failure and still lets the caller decide whether to retry, show a message, or stop the flow.

## 5. Practical Use Cases

- Debugging a form submission that fails only in production because a required field is missing.
- Recording API failures, such as timeouts, 500 responses, or invalid JSON.
- Tracing event handlers so you can see which user action led to a bug.
- Capturing unexpected values before they break business logic, such as `null` or `undefined`.
- Reporting crashes from a browser app so the team can prioritize the most common failures.
- Auditing important app events like login attempts, retries, and validation failures.

## 6. Common Mistakes

### Mistake 1: Logging too little context

Many beginners log only a short message, which makes production debugging much harder. A message like `"Error happened"` rarely tells you which user, value, or request caused the problem.

**Problem:** The log line does not include the values needed to reproduce the bug, so the error is hard to diagnose later.

```javascript
console.error("Error happened");
```

Output (tested with Node 24.21.0):

```text
Error happened
```

**Fix:** Include structured context that shows what the code was doing when the error occurred.

```javascript
console.error("Error happened while saving profile", {
  userId,
  profileId,
  attempt
});
```

Output (tested with Node 24.21.0):

```text
Uncaught ReferenceError: userId is not defined
```

The corrected version works better because it gives you the exact context needed for investigation.

### Mistake 2: Swallowing errors in catch blocks

Some code catches an error and does nothing with it. That makes failures disappear, which can hide broken logic and create silent data loss.

**Problem:** The error is caught, but the program continues as if nothing happened, so the failure becomes invisible.

```javascript
try {
  const data = JSON.parse(text);
  return data;
} catch (error) {
  // ignored
}
```

**Fix:** Log the failure, return a safe fallback, or rethrow it so the caller can handle it.

```javascript
try {
  const data = JSON.parse(text);
  return data;
} catch (error) {
  console.error("Could not parse JSON", error);
  return null;
}
```

Output (tested with Node 24.21.0):

```text
Could not parse JSON ReferenceError: text is not defined
```

The corrected version makes the failure visible and gives the caller a predictable result.

### Mistake 3: Throwing strings instead of Error objects

You can technically throw almost anything in JavaScript, but strings do not carry stack traces or standard error metadata. That makes production debugging much weaker.

**Problem:** Throwing a plain string removes the stack trace and makes the failure harder to inspect.

```javascript
function validateName(name) {
  if (!name) {
    throw "Name is required";
  }
}
```

**Fix:** Throw an `Error` or one of its subclasses.

```javascript
function validateName(name) {
  if (!name) {
    throw new Error("Name is required");
  }
}
```

The corrected version works better because standard error objects preserve stack traces and fit common tooling.

## 7. Best Practices

### Practice 1: Log at the right level

Use different log levels to separate routine information from warnings and failures. This makes large logs easier to scan and filter.

```javascript
console.info("Profile loaded");
console.warn("Using cached data");
console.error("Profile load failed");
```

Output (tested with Node 24.21.0):

```text
Profile loaded
Using cached data
Profile load failed
```

This pattern helps you separate expected warnings from genuine failures.

### Practice 2: Use structured values instead of long strings

Objects are easier to search and more useful for log aggregation than a single concatenated sentence.

```javascript
console.log("Checkout event", {
  orderId,
  total,
  currency
});
```

Output (tested with Node 24.21.0):

```text
Uncaught ReferenceError: orderId is not defined
```

Structured data is easier to filter by field and easier to reuse in monitoring tools.

### Practice 3: Rethrow when a lower layer cannot recover

If a function cannot meaningfully handle an error, it should log useful context and let the failure continue upward.

```javascript
function readConfig(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    console.error("Invalid config format", error);
    throw error;
  }
}
```

This keeps the failure visible while still adding local context.

## 8. Limitations and Edge Cases

- Logs in the browser console are mainly for developers; users do not normally see them.
- Messages can be lost or overwritten if the page crashes before telemetry is sent.
- Different browsers display objects and stack traces slightly differently.
- Minified production code can make stack traces harder to read unless source maps are enabled.
- `console.error()` does not automatically send data to your server or monitoring service.
- Unhandled promise rejections may not show up the same way as synchronous exceptions unless you track them explicitly.

**Problem:** A log message in the console does not mean the error has been recorded anywhere persistent.

For production tracking, you usually need both local logging and a system that stores errors remotely.

## 9. Practical Mini Project

Here is a small client-side feedback form that validates input, logs useful events, and reports errors to a centralized handler function.

```javascript
const errorBuffer = [];

function trackError(error, context) {
  const entry = {
    message: error.message,
    name: error.name,
    stack: error.stack,
    context
  };

  errorBuffer.push(entry);
  console.error("Tracked error", entry);
}

function submitFeedback(name, message) {
  try {
    if (!name || !message) {
      throw new Error("Both name and message are required.");
    }

    console.info("Feedback submitted", { name, message });
    return { ok: true };
  } catch (error) {
    trackError(error, {
      action: "submitFeedback",
      name,
      message
    });

    return { ok: false, error: error.message };
  }
}

const result = submitFeedback("Mina", "Great product");
console.log("Result", result);
```

Output (tested with Node 24.21.0):

```text
Feedback submitted { name: "Mina", message: "Great product" }
Result { ok: true }
```

This mini project shows a common pattern: validate input, log useful state, capture the error with context, and return a safe result to the caller.

## 10. Key Points

- Use logging to understand what your code is doing at runtime.
- Use errors to stop invalid work and make failures explicit.
- Use `try...catch` for recoverable problems such as parsing and network failures.
- Prefer `Error` objects over plain strings because they carry stack traces.
- Include enough context in logs to reproduce a bug later.
- For production, combine local logging with persistent error tracking.

## 11. Practice Exercise

- Write a function named `createOrderSummary` that accepts an array of item prices.
- Log the input array, reject non-arrays with a clear error, and return the total.
- If any price is not a finite number, throw a descriptive error.

**Expected output:** A number representing the total price, or a readable error message if the input is invalid.

**Hint:** Use `Array.isArray()`, `Number.isFinite()`, and `throw new Error()`.

**Solution:**

```javascript
function createOrderSummary(prices) {
  console.log("Order prices", prices);

  if (!Array.isArray(prices)) {
    throw new TypeError("prices must be an array.");
  }

  let total = 0;

  for (const price of prices) {
    if (!Number.isFinite(price)) {
      throw new RangeError("Every price must be a finite number.");
    }

    total += price;
  }

  console.info("Order total", total);
  return total;
}

try {
  const result = createOrderSummary([19.99, 5, 12.5]);
  console.log("Final result", result);
} catch (error) {
  console.error("Could not create order summary", error);
}
```

Output (tested with Node 24.21.0):

```text
Order prices [ 19.99, 5, 12.5 ]
Order total 37.489999999999995
Final result 37.489999999999995
```

## 12. Final Summary

JavaScript logging helps you see what is happening while your code runs, and error tracking helps you preserve failures in a way that is useful later. Together, they make debugging faster, reduce guesswork, and improve production reliability.

For everyday development, start with `console.log()`, then move to structured logging, `console.error()`, `try...catch`, and meaningful `Error` objects. For production systems, add persistent tracking so important failures are not lost when the page or process exits.

As a next step, practice adding logs to one small app, then replace one fragile silent failure with a real thrown error and a catch block that handles it cleanly.
