This page looks best with JavaScript enabled

Error Code Design and Unified Exception Handling in Django

 ·  ☕ 6 min read

I currently use Django for SaaS development, and I develop and maintain several SaaS applications at the same time. Many SaaS applications have agreed-upon error codes, some used to handle login state, others to mark business logic status. For a feature that is so strongly shared across projects, it is well worth spending time to study and research it. This post mainly discusses the purpose of error codes, how to design error codes, and how to use Django middleware to implement exception handling and the return of error codes.

1. The Purpose of Error Codes

Error codes are a set of numbers or letters associated with error information, used to agree on error states.

In a web application, a single API access involves reverse proxy forwarding, business logic processing, database access, template rendering, middleware processing, and other stages, so all kinds of errors are unavoidable. At the same time, the more complex the system, the longer the access path and the more modules there are, the higher the probability of errors.

Since errors cannot be avoided, the error information has to be reported back. Returning error information serves two purposes: on one hand, during the application development stage it makes debugging easier, allows adding the corresponding logic, and lets you prompt the user; on the other hand, when the application is running there may be potential exception risks, and error codes can assist in locating and fixing problems.

HTTP status codes are the most common — an agreed-upon set of codes for handling service state. An HTTP status code usually consists of three digits; the first digit defines the category of the response, and there are only five possible values.

Status CodeRangeMeaning
1xx100-101Informational – the request has been received, continue processing
2xx200-206Success – the request has been successfully received, understood, accepted
3xx300-305Redirection – the information is incomplete and needs to be supplemented further
4xx400-415Client error – the request has a syntax error or cannot be fulfilled
5xx500-505Server error – the server failed to fulfill a legitimate request

It is worth noting that HTTP status codes can be extended with a decimal to describe the server state in more detail; for example, 403 means access forbidden, 403.1 means executable access forbidden, and 403.2 means read access forbidden.

Through HTTP status codes, the client can effectively learn the response state of the server, better handle exceptional situations, and prompt the user with information. Error codes and HTTP status codes are similar in spirit; the difference is that error codes agree on business logic, while HTTP status codes agree on the server’s response state.

In the communication process of a web application, if the HTTP status code is not enough to express the server’s response state, an error code can supplement it; for example, the server returns {'code': 500101, 'message': u'连接数据库错误'}. The HTTP status code is a convention that has already reached consensus, whereas an error code requires a new convention to be established. In business logic, an HTTP status code can also express a business error state, for example using 412 to indicate that a precondition was not met. Error codes and HTTP status codes overlap, but they cannot replace each other.

2. How to Design Error Codes

2.1 Error Codes of Some Public Platforms

Error codes fall mainly into two categories

(1) Codes below 100, indicating that the user request does not pass basic validation, for example field validation, permissions, frequency, etc.

(2) Sub-error codes starting with “isp.”, indicating server-side exceptions, such as “isp.remote-service-error”, “isp.remote-service-timeout”, etc. Different services use different prefixes.

There are also some specially agreed error codes, such as 801, 802, etc.

Error code description:

(1) ret = 0, correct return

(2) ret > 0, an error occurred when calling the OpenAPI, and the developer needs to handle it accordingly.

(3) -50 <= ret <= -1, the API call could not pass the validation of the API proxy machine, and the developer needs to handle it accordingly.

(4) ret <-50, internal system error

In addition, the SDKs in various languages provided by the Tencent Open Platform share the same error code meanings. Numbers are used, for example 1801, 1802, 1803, etc.

Mainly three-digit and four-digit numbers; below are some of the error codes:

Error CodeError TypeDescription
0SuccessThe call succeeded
401<HTTP request parameters do not meet requirementsHTTP request parameters do not meet requirements
503Call quota exceededCall quota exceeded
504Service failureService failure
4000Invalid request parametersA required parameter is missing, or the parameter value format is incorrect
6000Internal server errorAn error occurred inside the server, please retry later or contact customer service for help.

The payment error codes of the Tencent Open Platform are expressed as three groups of numbers joined by hyphens. Judging from the letters of the error codes, hyphens separate different modules, or represent different processing stages, but the official documentation does not state this explicitly. Below are some of the error codes:

Error code: 1003-498493-106

Error code: 1003-498692-106

Error code: 1025-1025-0

Error code: 1043-10053-0

Error code: 1058-498198-40000

Error code: 1058-500952-40000

Error code: 1058-500954-40000

Error code format

1
2
3
4
5
6
JSON
{
	"request" : "/statuses/home_timeline.json",
	"error_code" : "20502",
	"error" : "Need you follow uid."
}

Error code description, taking 20502 as an example

20502
Service-level error (1 is a system-level error)Service module codeSpecific error code

Some error codes:

Error CodeError MessageDetailed Description
10014Service module Insufficient app permissionsThe application’s API access permission is restricted
20603List does not existsThe list does not exist
20701Repeated tag textCannot submit the same collection tag

The error codes of the Baidu Developer Center are encoded in an auto-incrementing way.

Error CodeError MessageDetailed Description
0SuccessSuccess
1Unknown errorUnknown error
2Service temporarily unavailableService temporarily unavailable
100Invalid parameterInvalid parameter
101Invalid api keyInvalid API key
102Invalid session keySession key invalid or no longer valid
103Invalid call_id parameterInvalid/Used call id parameter

The WeChat Open Platform uses five-digit error codes.

Error CodeError MessageDetailed Description
40001invalid credentialInvalid call credential
40008invalid message typeInvalid message_type
40016invalid button sizeInvalid number of menu buttons

In the WeChat Pay related APIs, codes are encoded with uppercase English letters plus underscores.

Error CodeError MessageDetailed Description
NOAUTHThe merchant has no permission for this APIThe merchant has not enabled the permission for this API
ORDERPAIDThe merchant’s order has been paid, no need to repeat the operationThe merchant’s order has been paid, no further operation needed
SYSTEMERRORSystem errorSystem timeout

2.2 What Makes a Good Error Code

  • Short enough

While meeting usage requirements and considering extensibility, a short error code is easier to maintain and update. The error codes of the Tencent Open Platform look particularly verbose; even if you have encountered an error once, it is still hard to recall on its second appearance.

  • Contains more information

The error codes of the Sina Open Platform distinguish system-level and service-level errors by the first digit. This is followed closely by the module code and the specific error code, making it very easy to locate the error. Containing more information means a longer error code, which conflicts with the advice to keep it short enough. How to choose a suitable length needs to take the complexity of the system into account. If the system is very complex and needs to represent many states, then of course you should prioritize the system’s needs and use longer error codes that contain more error information.

  • Literal and self-explanatory
    The error codes of the WeChat Pay platform are especially easy to understand. Through a few simple actions and keyword combinations, such as NO, LACK, DATA, PARAMS, you can guess the meaning of the error with near accuracy without a code lookup table. Of course, there are also some rather long error codes, like OUT_TRADE_NO_USED, which are more laborious to encode and understand.

  • Make full use of codes that have reached consensus

Returning 0 means the request is normal, returning <0 means an exception — no textual explanation is needed, and using consensus codes can significantly reduce communication costs. Note that there is another consensus, especially for web developers: the HTTP status code is the most important coding consensus. Both the Tencent Open Platform and the WeChat Open Platform use a large number of 4XXX codes for client errors and 5XXX for internal server errors. Without looking at an error code lookup table, developers can basically locate where the error occurred, and then use the lookup table to pin it down to the error in the program logic.

2.3 Error Code Design

2.3.1 Encoding by Module

1st digit2nd-3rd digit4th-5th digit
20502
Service-level error (1 is a system-level error)Service module codeSpecific error code

Divide the error codes into sections, use different sections to represent different modules, and then encode the errors. The number of error codes available with this encoding is subject to certain limits; for example, when 10100-10199 are used up, you are forced to occupy codes starting with 102 and 103. Of course, you can also reserve sufficient encoding space when designing error codes. For example:

1st digit2nd-4th digit5th-8th digit
20500200

2.3.2 Encoding with English Phrases

Common system error codes all use only Arabic numerals; for example, the error codes in the Windows system increase from 0000 to 15999. The advantage of using numbers is high processing efficiency and easy encoding. However, the meaning a single number can express is limited. If a phrase can be used to give the error prompt directly, it is more direct and effective.

1
2
3
4
5
ERROR_INVALID_FUNCTION
ERROR_INVALID_FUNCTION
ERROR_PATH_NOT_FOUND
ERROR_TOO_MANY_OPEN_FILES
ERROR_ACCESS_DENIED

At the code level, the difference between encoding with English phrases and encoding with numbers is

1
2
if (code == "10100")
if (code == "ERROR_ACCESS_DENIED")

2.3.3 Encoding with a State Diagram

The essence of an application system is a finite state machine, and an error code represents one error state of the application system. Designing error codes means encoding the states of the application system.

Take a simple shopping web system as an example. The application system has only three logical modules: login, precondition check, and payment.

At this point, the application system has three nodes — login, front, pay — and six paths, ①②③④⑤.

1
2
3
4
# 路径 - ②⑤
ERROR_LOGIN_FRONT_NOT_XXX
# 路径 - ②③④
ERROR_LOGIN_FRONT_PAY_NOT_XXX

If a new processing node is added, exchange

1
2
# 路径 - ②⑥
SUCCESS_LOGIN_FRONT_EXCHANGE

The advantage of encoding errors through a state diagram is that it can describe very precisely where the error occurred, and when the system is extended you only need to add nodes and edges. Here you can of course also use numbers for encoding, for example node login (100), front (101), path - ②⑤ (100101XXX).

3. How Django Handles Exceptions

When Debug = True, if an exception occurs, Django echoes the relevant information from program runtime onto the page, making it easier for developers to debug. As shown below:

When Debug = False, if an exception occurs, Django returns a custom or built-in 500, 404, etc. page. As shown below:

Now let’s look at how Django handles these exceptions:

3.1 How Django Handles a request

Taking local development as an example, when the browser initiates a request, the wsgi in Django creates a WSGIHandler object to handle the request. In the
WSGIHandler object the environment variables are initialized; if there is no exception, the self.get_response(request) function is called to handle the request and returns the response to wsgi.

get_response is defined in the django.core.handlers.base.py file; the processing flow is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
	for middleware_method in self._request_middleware:
		response = middleware_method(request)
		if response:
			break
	...
	if response is None:
	...
		for middleware_method in self._view_middleware:
			response = middleware_method(request, callback, callback_args, callback_kwargs)
			if response:
				break
	...
	response = wrapped_callback(request, *callback_args, **callback_kwargs)
	...
	if response is None:
		try:
			response = wrapped_callback(request, *callback_args, **callback_kwargs)
		except Exception as e:
			for middleware_method in self._exception_middleware:
				response = middleware_method(request, e)
				if response:
					break
			if response is None:
				raise
	...
	for middleware_method in self._response_middleware:
		response = middleware_method(request, response)
	...
	return response

This diagram presents the whole processing flow logic fairly well.

3.2 ExceptionBox

Django’s middleware supports a kind of Exception form. When an uncaught exception occurs, the function process_exception defined in the middleware is executed; if it returns a response, then the whole flow can be ended.

In a Django project, a module for unified exception handling and error code management is needed. Hence ExceptionBox.

The data return format:

1
2
3
4
5
6
{
	'code': 'XXXXXX',
	'message': '错误提示XXXX',
	'result': False,
	'data': None
}

__init__.py

1
2
# -*- coding: utf-8 -*-
from .error import *

base.py

1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
from abc import ABCMeta

class BaseReturn(Exception):
    __metaclass__ = ABCMeta

class PreconditionFailed412(BaseReturn):
    status_code = 412

error.py

1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from . import base

# Example
class ERROR_LOGIN_FRONT_NOT_GIFT(base.PreconditionFailed412):
    message = "礼品不充足"

middleware.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# -*- coding: utf-8 -*-
import json
import logging
import traceback

from django.http import JsonResponse

from .base import BaseReturn

logger = logging.getLogger('root')


class ExceptionBoxMiddleware(object):
    def process_exception(self, request, exception):
        if not issubclass(exception.__class__, BaseReturn):
            return None
        ret_json = {
            'code': exception.__class__.__name__,
            'message': getattr(exception, 'message', 'error'),
            'result': False,
            'data': None
        }
        response = JsonResponse(ret_json)
        response.status_code = getattr(exception, 'status_code', 500)
        _logger = logger.error if response.status_code >= 500 else logger.warning
        _logger('status_code->{status_code}, error_code->{code}, url->{url}, '
                'method->{method}, param->{param}, '
                'body->{body},traceback->{traceback}'.format(
            status_code=response.status_code, code=ret_json['code'], url=request.path,
            method=request.method, param=json.dumps(getattr(request, request.method, {})),
            body=request.body, traceback=traceback.format_exc()
        ))
        return response

my_view.py

1
2
3
import exceptionbox
def home_view(request):
    raise exceptionbox.ERROR_LOGIN_FRONT_NOT_GIFT()

4. Reference


微信公众号
WRITTEN BY
微信公众号